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_STREAMS_CONTAINER_SCHEMA_NODE = "streams"
64 val static RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE = "stream"
65 val static RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations"
66 val static SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"
67 val static SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription"
73 extension ControllerContext controllerContext
76 if (INSTANCE !== null) {
77 throw new IllegalStateException("Already instantiated");
81 static def getInstance() {
85 override getModules() {
86 val restconfModule = getRestconfModule()
87 val List<Node<?>> modulesAsData = new ArrayList
88 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
89 for (module : allModules) {
90 modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode))
92 val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)
93 val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData)
94 return new StructuredData(modulesNode, modulesSchemaNode, null)
97 override getAvailableStreams(){
98 var Set<String> availableStreams = Notificator.getStreamNames();
99 val List<Node<?>> streamsAsData = new ArrayList
100 val streamSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE)
101 for (String streamName:availableStreams){
102 streamsAsData.add(streamName.toStreamCompositeNode(streamSchemaNode))
104 val streamsSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE)
105 val streamsNode = NodeFactory.createImmutableCompositeNode(streamsSchemaNode.QName, null, streamsAsData)
106 return new StructuredData(streamsNode, streamsSchemaNode, null)
108 override getModules(String identifier) {
109 var Set<Module> modules = null
110 var MountInstance mountPoint = null
111 if (identifier.contains(ControllerContext.MOUNT)) {
112 mountPoint = identifier.toMountPointIdentifier.mountPoint
113 modules = mountPoint.allModules
115 throw new ResponseException(BAD_REQUEST, "URI has bad format. If modules behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT)
117 val List<Node<?>> modulesAsData = new ArrayList
118 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
119 for (module : modules) {
120 modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode))
122 val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)
123 val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData)
124 return new StructuredData(modulesNode, modulesSchemaNode, mountPoint)
127 override getModule(String identifier) {
128 val moduleNameAndRevision = identifier.moduleNameAndRevision
129 var Module module = null
130 var MountInstance mountPoint = null
131 if (identifier.contains(ControllerContext.MOUNT)) {
132 mountPoint = identifier.toMountPointIdentifier.mountPoint
133 module = mountPoint.findModuleByNameAndRevision(moduleNameAndRevision)
135 module = findModuleByNameAndRevision(moduleNameAndRevision)
137 if (module === null) {
138 throw new ResponseException(BAD_REQUEST,
139 "Module with name '" + moduleNameAndRevision.localName + "' and revision '" +
140 moduleNameAndRevision.revision + "' was not found.")
142 val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)
143 val moduleNode = module.toModuleCompositeNode(moduleSchemaNode)
144 return new StructuredData(moduleNode, moduleSchemaNode, mountPoint)
147 override getOperations() {
148 return operationsFromModulesToStructuredData(allModules,null)
151 override getOperations(String identifier) {
152 var Set<Module> modules = null
153 var MountInstance mountPoint = null
154 if (identifier.contains(ControllerContext.MOUNT)) {
155 mountPoint = identifier.toMountPointIdentifier.mountPoint
156 modules = mountPoint.allModules
158 throw new ResponseException(BAD_REQUEST, "URI has bad format. If operations behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT)
160 return operationsFromModulesToStructuredData(modules,mountPoint)
163 private def StructuredData operationsFromModulesToStructuredData(Set<Module> modules,MountInstance mountPoint) {
164 val List<Node<?>> operationsAsData = new ArrayList
165 val operationsSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE)
166 val fakeOperationsSchemaNode = new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, operationsSchemaNode.QName, operationsSchemaNode.path)
167 for (module : modules) {
168 for (rpc : module.rpcs) {
169 operationsAsData.add(NodeFactory.createImmutableSimpleNode(rpc.QName, null, null))
170 val fakeRpcSchemaNode = new LeafSchemaNodeBuilder(module.name, 0, rpc.QName, null)
171 fakeRpcSchemaNode.setAugmenting(true)
172 fakeRpcSchemaNode.setType(EmptyType.instance)
173 fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build)
176 val operationsNode = NodeFactory.createImmutableCompositeNode(operationsSchemaNode.QName, null, operationsAsData)
177 return new StructuredData(operationsNode, fakeOperationsSchemaNode.build, mountPoint)
180 private def Module getRestconfModule() {
181 val restconfModule = findModuleByNameAndRevision(
182 QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION,
183 RESTCONF_MODULE_DRAFT02_NAME))
184 if (restconfModule === null) {
185 throw new ResponseException(INTERNAL_SERVER_ERROR, "Restconf module was not found.")
187 return restconfModule
190 private def QName getModuleNameAndRevision(String identifier) {
191 val indexOfMountPointFirstLetter = identifier.indexOf(ControllerContext.MOUNT)
192 var moduleNameAndRevision = "";
193 if (indexOfMountPointFirstLetter !== -1) { // module and revision is behind mount point string
194 moduleNameAndRevision = identifier.substring(indexOfMountPointFirstLetter + ControllerContext.MOUNT.length)
196 moduleNameAndRevision = identifier
198 val pathArgs = Lists.newArrayList(Splitter.on("/").omitEmptyStrings.split(moduleNameAndRevision))
199 if (pathArgs.length < 2) {
200 throw new ResponseException(BAD_REQUEST,
201 "URI has bad format. End of URI should be in format 'moduleName/yyyy-MM-dd'")
204 val moduleName = pathArgs.head
205 val moduleRevision = REVISION_FORMAT.parse(pathArgs.get(1))
206 return QName.create(null, moduleRevision, moduleName)
207 } catch(ParseException e) {
208 throw new ResponseException(BAD_REQUEST, "URI has bad format. It should be 'moduleName/yyyy-MM-dd'")
212 private def CompositeNode toStreamCompositeNode(String streamName, DataSchemaNode streamSchemaNode) {
213 val List<Node<?>> streamNodeValues = new ArrayList
214 val nameSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("name").head
215 streamNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.QName, null, streamName))
217 val descriptionSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("description").head
218 streamNodeValues.add(NodeFactory.createImmutableSimpleNode(descriptionSchemaNode.QName, null, "DESCRIPTION_PLACEHOLDER"))
220 val replaySupportSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("replay-support").head
221 streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replaySupportSchemaNode.QName, null, true))
223 val replayLogCreationTimeSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("replay-log-creation-time").head
224 streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replayLogCreationTimeSchemaNode.QName, null, ""))
226 val eventsSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("events").head
227 streamNodeValues.add(NodeFactory.createImmutableSimpleNode(eventsSchemaNode.QName, null, ""))
229 return NodeFactory.createImmutableCompositeNode(streamSchemaNode.QName, null, streamNodeValues)
231 private def CompositeNode toModuleCompositeNode(Module module, DataSchemaNode moduleSchemaNode) {
232 val List<Node<?>> moduleNodeValues = new ArrayList
233 val nameSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("name").head
234 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.QName, null, module.name))
235 val revisionSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("revision").head
236 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(revisionSchemaNode.QName, null, REVISION_FORMAT.format(module.revision)))
237 val namespaceSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("namespace").head
238 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(namespaceSchemaNode.QName, null, module.namespace.toString))
239 val featureSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("feature").head
240 for (feature : module.features) {
241 moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(featureSchemaNode.QName, null, feature.QName.localName))
243 return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.QName, null, moduleNodeValues)
246 private def DataSchemaNode getSchemaNode(Module restconfModule, String schemaNodeName) {
247 val restconfGrouping = restconfModule.groupings.filter[g|g.QName.localName == RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE].head
248 val restconfContainer = restconfGrouping.findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE).head
249 if (schemaNodeName == RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE) {
250 return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE).head
251 } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE) {
252 return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE).head
253 } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE) {
254 val modules = (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE).head
255 return (modules as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE).head
256 }else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE) {
257 return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head
258 } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) {
259 val modules = (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head
260 return (modules as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE).head
269 override invokeRpc(String identifier, CompositeNode payload) {
270 val rpc = resolveIdentifierInInvokeRpc(identifier)
271 if (rpc.QName.namespace.toString == SAL_REMOTE_NAMESPACE && rpc.QName.localName == SAL_REMOTE_RPC_SUBSRCIBE) {
272 val value = normalizeNode(payload, rpc.input, null)
273 val pathNode = value?.getFirstSimpleByName(QName.create(rpc.QName, "path"))
274 val pathValue = pathNode?.value
275 if (pathValue === null && !(pathValue instanceof InstanceIdentifier)) {
276 throw new ResponseException(INTERNAL_SERVER_ERROR, "Instance identifier was not normalized correctly.");
278 val pathIdentifier = (pathValue as InstanceIdentifier)
279 var String streamName = null
280 if (!pathIdentifier.path.nullOrEmpty) {
281 streamName = Notificator.createStreamNameFromUri(pathIdentifier.toFullRestconfIdentifier)
283 if (streamName.nullOrEmpty) {
284 throw new ResponseException(BAD_REQUEST, "Path is empty or contains data node which is not Container or List build-in type.");
286 val streamNameNode = NodeFactory.createImmutableSimpleNode(QName.create(rpc.output.QName, "stream-name"), null, streamName)
287 val List<Node<?>> output = new ArrayList
288 output.add(streamNameNode)
289 val responseData = NodeFactory.createMutableCompositeNode(rpc.output.QName, null, output, null, null)
291 if (!Notificator.existListenerFor(pathIdentifier)) {
292 Notificator.createListener(pathIdentifier, streamName)
295 return new StructuredData(responseData, rpc.output, null)
297 return callRpc(identifier.rpcDefinition, payload)
300 override invokeRpc(String identifier, String noPayload) {
301 if (!noPayload.nullOrEmpty) {
302 throw new ResponseException(UNSUPPORTED_MEDIA_TYPE, "Content-Type contains unsupported Media Type.");
304 val rpc = resolveIdentifierInInvokeRpc(identifier)
305 return callRpc(rpc, null)
308 private def resolveIdentifierInInvokeRpc(String identifier) {
309 if (identifier.indexOf("/") === -1) {
310 val identifierDecoded = identifier.urlPathArgDecode
311 val rpc = identifierDecoded.rpcDefinition
315 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
317 val slashErrorMsg = String.format(
318 "Identifier %n%s%ncan't contain slash character (/).%nIf slash is part of identifier name then use %%2F placeholder.", identifier)
319 throw new ResponseException(NOT_FOUND, slashErrorMsg);
322 private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) {
324 throw new ResponseException(NOT_FOUND, "RPC does not exist.");
326 var CompositeNode rpcRequest;
327 if (payload === null) {
328 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null)
330 val value = normalizeNode(payload, rpc.input, null)
331 val List<Node<?>> input = new ArrayList
333 rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null)
335 val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest);
336 if (!rpcResult.successful) {
337 throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed")
339 if (rpcResult.result === null) {
342 return new StructuredData(rpcResult.result, rpc.output, null)
345 override readConfigurationData(String identifier) {
346 val iiWithData = identifier.toInstanceIdentifier
347 var CompositeNode data = null;
348 if (iiWithData.mountPoint !== null) {
349 data = broker.readConfigurationDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
351 data = broker.readConfigurationData(iiWithData.getInstanceIdentifier);
353 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
356 override readOperationalData(String identifier) {
357 val iiWithData = identifier.toInstanceIdentifier
358 var CompositeNode data = null;
359 if (iiWithData.mountPoint !== null) {
360 data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier)
362 data = broker.readOperationalData(iiWithData.getInstanceIdentifier);
364 return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint)
367 override updateConfigurationData(String identifier, CompositeNode payload) {
368 val iiWithData = identifier.toInstanceIdentifier
369 val value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
370 var RpcResult<TransactionStatus> status = null
371 if (iiWithData.mountPoint !== null) {
372 status = broker.commitConfigurationDataPutBehindMountPoint(iiWithData.mountPoint,
373 iiWithData.instanceIdentifier, value).get()
375 status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get();
377 switch status.result {
378 case TransactionStatus.COMMITED: Response.status(OK).build
379 default: Response.status(INTERNAL_SERVER_ERROR).build
383 override createConfigurationData(String identifier, CompositeNode payload) {
384 if (payload.namespace === null) {
385 throw new ResponseException(BAD_REQUEST,
386 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
388 var InstanceIdWithSchemaNode iiWithData;
389 var CompositeNode value;
390 if (payload.representsMountPointRootData) { // payload represents mount point data and URI represents path to the mount point
391 if (identifier.endsWithMountPoint) {
392 throw new ResponseException(BAD_REQUEST,
393 "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + "\" for POST operation.");
395 val completIdentifier = identifier.addMountPointIdentifier
396 iiWithData = completIdentifier.toInstanceIdentifier
397 value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint)
399 val uncompleteInstIdWithData = identifier.toInstanceIdentifier
400 val parentSchema = uncompleteInstIdWithData.schemaNode as DataNodeContainer
401 val module = uncompleteInstIdWithData.mountPoint.findModule(payload)
402 if (module === null) {
403 throw new ResponseException(BAD_REQUEST, "Module was not found for \"" + payload.namespace + "\"")
405 val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
406 value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint)
407 iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode)
409 var RpcResult<TransactionStatus> status = null
410 if (iiWithData.mountPoint !== null) {
411 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
412 iiWithData.instanceIdentifier, value)?.get();
414 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
416 if (status === null) {
417 return Response.status(ACCEPTED).build
419 switch status.result {
420 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
421 default: Response.status(INTERNAL_SERVER_ERROR).build
425 override createConfigurationData(CompositeNode payload) {
426 if (payload.namespace === null) {
427 throw new ResponseException(BAD_REQUEST,
428 "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)");
430 val module = findModule(null, payload)
431 if (module === null) {
432 throw new ResponseException(BAD_REQUEST,
433 "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)");
435 val schemaNode = module.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace)
436 val value = normalizeNode(payload, schemaNode, null)
437 val iiWithData = addLastIdentifierFromData(null, value, schemaNode)
438 var RpcResult<TransactionStatus> status = null
439 if (iiWithData.mountPoint !== null) {
440 status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint,
441 iiWithData.instanceIdentifier, value)?.get();
443 status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get();
445 if (status === null) {
446 return Response.status(ACCEPTED).build
448 switch status.result {
449 case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build
450 default: Response.status(INTERNAL_SERVER_ERROR).build
454 override deleteConfigurationData(String identifier) {
455 val iiWithData = identifier.toInstanceIdentifier
456 var RpcResult<TransactionStatus> status = null
457 if (iiWithData.mountPoint !== null) {
458 status = broker.commitConfigurationDataDeleteBehindMountPoint(iiWithData.mountPoint,
459 iiWithData.getInstanceIdentifier).get;
461 status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get;
463 switch status.result {
464 case TransactionStatus.COMMITED: Response.status(OK).build
465 default: Response.status(INTERNAL_SERVER_ERROR).build
469 override subscribeToStream(String identifier, UriInfo uriInfo) {
470 val streamName = Notificator.createStreamNameFromUri(identifier)
471 if (streamName.nullOrEmpty) {
472 throw new ResponseException(BAD_REQUEST, "Stream name is empty.")
474 val listener = Notificator.getListenerFor(streamName);
475 if (listener === null) {
476 throw new ResponseException(BAD_REQUEST, "Stream was not found.")
478 broker.registerToListenDataChanges(listener)
479 val uriBuilder = uriInfo.getAbsolutePathBuilder()
480 val uriToWebsocketServer = uriBuilder.port(WebSocketServer.PORT).replacePath(streamName).build()
481 return Response.status(OK).location(uriToWebsocketServer).build
484 private def dispatch URI namespace(CompositeNode data) {
485 return data.nodeType.namespace
488 private def dispatch URI namespace(CompositeNodeWrapper data) {
489 return data.namespace
492 private def dispatch String localName(CompositeNode data) {
493 return data.nodeType.localName
496 private def dispatch String localName(CompositeNodeWrapper data) {
497 return data.localName
500 private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) {
501 if (mountPoint !== null) {
502 return mountPoint.findModuleByNamespace(data.nodeType.namespace)
504 return findModuleByNamespace(data.nodeType.namespace)
508 private def dispatch Module findModule(MountInstance mountPoint, CompositeNodeWrapper data) {
509 Preconditions.checkNotNull(data.namespace)
510 var Module module = null;
511 if (mountPoint !== null) {
512 module = mountPoint.findModuleByNamespace(data.namespace) // namespace from XML
513 if (module === null) {
514 module = mountPoint.findModuleByName(data.namespace.toString) // namespace (module name) from JSON
517 module = data.namespace.findModuleByNamespace // namespace from XML
518 if (module === null) {
519 module = data.namespace.toString.findModuleByName // namespace (module name) from JSON
525 private def dispatch getName(CompositeNode data) {
526 return data.nodeType.localName
529 private def dispatch getName(CompositeNodeWrapper data) {
530 return data.localName
533 private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode,
534 CompositeNode data, DataSchemaNode schemaOfData) {
535 val iiOriginal = identifierWithSchemaNode?.instanceIdentifier
536 var InstanceIdentifierBuilder iiBuilder = null
537 if (iiOriginal === null) {
538 iiBuilder = InstanceIdentifier.builder
540 iiBuilder = InstanceIdentifier.builder(iiOriginal)
543 if (schemaOfData instanceof ListSchemaNode) {
544 iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data))
546 iiBuilder.node(schemaOfData.QName)
548 return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint)
551 private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) {
552 val keyValues = new HashMap<QName, Object>();
553 for (key : listNode.keyDefinition) {
554 val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value
555 if (dataNodeKeyValueObject === null) {
556 throw new ResponseException(BAD_REQUEST,
557 "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" +
558 key.localName + "\"")
560 keyValues.put(key, dataNodeKeyValueObject);
565 private def endsWithMountPoint(String identifier) {
566 return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/"))
569 private def representsMountPointRootData(CompositeNode data) {
570 return ((data.namespace == SchemaContext.NAME.namespace || data.namespace == MOUNT_POINT_MODULE_NAME) &&
571 data.localName == SchemaContext.NAME.localName)
574 private def addMountPointIdentifier(String identifier) {
575 if (identifier.endsWith("/")) {
576 return identifier + ControllerContext.MOUNT
578 return identifier + "/" + ControllerContext.MOUNT
581 private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, MountInstance mountPoint) {
582 if (schema === null) {
583 throw new ResponseException(INTERNAL_SERVER_ERROR,
584 "Data schema node was not found for " + node?.nodeType?.localName)
586 if (!(schema instanceof DataNodeContainer)) {
587 throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype.");
589 if (node instanceof CompositeNodeWrapper) {
590 if ((node as CompositeNodeWrapper).changeAllowed) {
591 normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint)
593 return (node as CompositeNodeWrapper).unwrap()
598 private def void normalizeNode(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
599 MountInstance mountPoint) {
600 if (schema === null) {
601 throw new ResponseException(BAD_REQUEST,
602 "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema.");
605 var QName currentAugment;
606 if (nodeBuilder.qname !== null) {
607 currentAugment = previousAugment
609 currentAugment = normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint)
610 if (nodeBuilder.qname === null) {
611 throw new ResponseException(BAD_REQUEST,
612 "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName +
613 "\" should be \"" + schema.QName.namespace + "\".\n" +
614 "If data is in JSON format then module name for \"" + nodeBuilder.localName +
615 "\" should be corresponding to namespace \"" + schema.QName.namespace + "\".");
619 if (nodeBuilder instanceof CompositeNodeWrapper) {
620 val List<NodeWrapper<?>> children = (nodeBuilder as CompositeNodeWrapper).getValues
621 for (child : children) {
622 val potentialSchemaNodes = (schema as DataNodeContainer).findInstanceDataChildrenByName(child.localName)
623 if (potentialSchemaNodes.size > 1 && child.namespace === null) {
624 val StringBuilder namespacesOfPotentialModules = new StringBuilder;
625 for (potentialSchemaNode : potentialSchemaNodes) {
626 namespacesOfPotentialModules.append(" ").append(potentialSchemaNode.QName.namespace.toString).append("\n")
628 throw new ResponseException(BAD_REQUEST,
629 "Node \"" + child.localName + "\" is added as augment from more than one module. "
630 + "Therefore node must have namespace (XML format) or module name (JSON format)."
631 + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules)
633 var rightNodeSchemaFound = false
634 for (potentialSchemaNode : potentialSchemaNodes) {
635 if (!rightNodeSchemaFound) {
636 val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment,
638 if (child.qname !== null) {
639 normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint)
640 rightNodeSchemaFound = true
644 if (!rightNodeSchemaFound) {
645 throw new ResponseException(BAD_REQUEST,
646 "Schema node \"" + child.localName + "\" was not found in module.")
649 if (schema instanceof ListSchemaNode) {
650 val listKeys = (schema as ListSchemaNode).keyDefinition
651 for (listKey : listKeys) {
653 for (child : children) {
654 if (child.unwrap.nodeType.localName == listKey.localName) {
659 throw new ResponseException(BAD_REQUEST,
660 "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName +
665 } else if (nodeBuilder instanceof SimpleNodeWrapper) {
666 val simpleNode = (nodeBuilder as SimpleNodeWrapper)
667 val value = simpleNode.value
668 var inputValue = value;
670 if (schema.typeDefinition instanceof IdentityrefTypeDefinition) {
671 if (value instanceof String) {
672 inputValue = new IdentityValuesDTO(nodeBuilder.namespace.toString, value as String, null)
673 } // else value is already instance of IdentityValuesDTO
676 val outputValue = RestCodec.from(schema.typeDefinition, mountPoint)?.deserialize(inputValue);
677 simpleNode.setValue(outputValue)
678 } else if (nodeBuilder instanceof EmptyNodeWrapper) {
679 val emptyNodeBuilder = nodeBuilder as EmptyNodeWrapper
680 if (schema instanceof LeafSchemaNode) {
681 emptyNodeBuilder.setComposite(false);
682 } else if (schema instanceof ContainerSchemaNode) {
684 // FIXME: Add presence check
685 emptyNodeBuilder.setComposite(true);
690 private def dispatch TypeDefinition<?> typeDefinition(LeafSchemaNode node) {
691 var baseType = node.type
692 while (baseType.baseType !== null) {
693 baseType = baseType.baseType;
698 private def dispatch TypeDefinition<?> typeDefinition(LeafListSchemaNode node) {
699 var TypeDefinition<?> baseType = node.type
700 while (baseType.baseType !== null) {
701 baseType = baseType.baseType;
706 private def QName normalizeNodeName(NodeWrapper<?> nodeBuilder, DataSchemaNode schema, QName previousAugment,
707 MountInstance mountPoint) {
708 var validQName = schema.QName
709 var currentAugment = previousAugment;
710 if (schema.augmenting) {
711 currentAugment = schema.QName
712 } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) {
713 validQName = QName.create(currentAugment, schema.QName.localName);
715 var String moduleName = null;
716 if (mountPoint === null) {
717 moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace);
719 moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace)
721 if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace ||
722 nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) {
723 nodeBuilder.qname = validQName
725 return currentAugment