From 3b57f8f6b29cd8e63e20ad81ba87fdc24828f7ab Mon Sep 17 00:00:00 2001 From: tpantelis Date: Sun, 4 May 2014 13:29:22 -0400 Subject: [PATCH 1/1] Bug 617: Remove extend files from sal-rest-connector Converted ControllerContext and BrokerFacade xtend code to java. Addressed review comments: - Removed SupressWarnings "all" - Converted use of xext classes to guava - Removed sneakyThrow Patch set 4: mistake - no changes Patch set 5: Converted RestconfImpl Patch set 6: removed xtend plugin anf dependencies from pom file Change-Id: I45c22b22fee07a178faba9fcb9e52d3ff12a6697 Signed-off-by: tpantelis --- .../md-sal/sal-rest-connector/pom.xml | 13 +- .../sal/restconf/impl/BrokerFacade.java | 207 +++ .../sal/restconf/impl/BrokerFacade.xtend | 146 -- .../sal/restconf/impl/ControllerContext.java | 912 +++++++++++++ .../sal/restconf/impl/ControllerContext.xtend | 637 --------- .../sal/restconf/impl/ResponseException.java | 5 + .../sal/restconf/impl/RestconfImpl.java | 1170 +++++++++++++++++ .../sal/restconf/impl/RestconfImpl.xtend | 732 ----------- .../restconf/impl/test/BrokerFacadeTest.java | 327 +++++ 9 files changed, 2622 insertions(+), 1527 deletions(-) create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java delete mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java delete mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java delete mode 100644 opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend create mode 100644 opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java diff --git a/opendaylight/md-sal/sal-rest-connector/pom.xml b/opendaylight/md-sal/sal-rest-connector/pom.xml index d6ec2fd74d..fe00ab1836 100644 --- a/opendaylight/md-sal/sal-rest-connector/pom.xml +++ b/opendaylight/md-sal/sal-rest-connector/pom.xml @@ -27,10 +27,6 @@ io.netty netty-codec-http - - org.eclipse.xtend - org.eclipse.xtend.lib - org.opendaylight.controller sal-remote @@ -92,10 +88,7 @@ MD SAL Restconf Connector org.opendaylight.controller.sal.rest.*, - org.opendaylight.controller.sal.restconf.impl, - org.eclipse.xtend2.lib, - org.eclipse.xtend.lib, - org.eclipse.xtext.xbase.*, + org.opendaylight.controller.sal.restconf.impl, *, com.sun.jersey.spi.container.servlet org.opendaylight.controller.sal.rest.impl.RestconfProvider @@ -103,10 +96,6 @@ - - org.eclipse.xtend - xtend-maven-plugin - diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java new file mode 100644 index 0000000000..68c9340bb1 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.java @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.restconf.impl; + +import com.google.common.base.Objects; + +import java.util.Map; +import java.util.concurrent.Future; + +import javax.ws.rs.core.Response.Status; + +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.md.sal.common.api.data.DataReader; +import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession; +import org.opendaylight.controller.sal.core.api.data.DataBrokerService; +import org.opendaylight.controller.sal.core.api.data.DataChangeListener; +import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction; +import org.opendaylight.controller.sal.core.api.mount.MountInstance; +import org.opendaylight.controller.sal.rest.impl.RestconfProvider; +import org.opendaylight.controller.sal.restconf.impl.ResponseException; +import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BrokerFacade implements DataReader { + private final static Logger LOG = LoggerFactory.getLogger( BrokerFacade.class ); + + private final static BrokerFacade INSTANCE = new BrokerFacade(); + + private volatile DataBrokerService dataService; + private volatile ConsumerSession context; + + private BrokerFacade() { + } + + public void setContext( final ConsumerSession context ) { + this.context = context; + } + + public void setDataService( final DataBrokerService dataService ) { + this.dataService = dataService; + } + + public static BrokerFacade getInstance() { + return BrokerFacade.INSTANCE; + } + + private void checkPreconditions() { + if( context == null || dataService == null ) { + ResponseException _responseException = new ResponseException( Status.SERVICE_UNAVAILABLE, + RestconfProvider.NOT_INITALIZED_MSG ); + throw _responseException; + } + } + + @Override + public CompositeNode readConfigurationData( final InstanceIdentifier path ) { + this.checkPreconditions(); + + LOG.trace( "Read Configuration via Restconf: {}", path ); + + return dataService.readConfigurationData( path ); + } + + public CompositeNode readConfigurationDataBehindMountPoint( final MountInstance mountPoint, + final InstanceIdentifier path ) { + this.checkPreconditions(); + + LOG.trace( "Read Configuration via Restconf: {}", path ); + + return mountPoint.readConfigurationData( path ); + } + + @Override + public CompositeNode readOperationalData( final InstanceIdentifier path ) { + this.checkPreconditions(); + + BrokerFacade.LOG.trace( "Read Operational via Restconf: {}", path ); + + return dataService.readOperationalData( path ); + } + + public CompositeNode readOperationalDataBehindMountPoint( final MountInstance mountPoint, + final InstanceIdentifier path ) { + this.checkPreconditions(); + + BrokerFacade.LOG.trace( "Read Operational via Restconf: {}", path ); + + return mountPoint.readOperationalData( path ); + } + + public RpcResult invokeRpc( final QName type, final CompositeNode payload ) { + this.checkPreconditions(); + + final Future> future = context.rpc( type, payload ); + + try { + return future.get(); + } + catch( Exception e ) { + throw new ResponseException( e, "Error invoking RPC " + type ); + } + } + + public Future> commitConfigurationDataPut( final InstanceIdentifier path, + final CompositeNode payload ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = dataService.beginTransaction(); + BrokerFacade.LOG.trace( "Put Configuration via Restconf: {}", path ); + transaction.putConfigurationData( path, payload ); + return transaction.commit(); + } + + public Future> commitConfigurationDataPutBehindMountPoint( + final MountInstance mountPoint, final InstanceIdentifier path, final CompositeNode payload ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = mountPoint.beginTransaction(); + BrokerFacade.LOG.trace( "Put Configuration via Restconf: {}", path ); + transaction.putConfigurationData( path, payload ); + return transaction.commit(); + } + + public Future> commitConfigurationDataPost( final InstanceIdentifier path, + final CompositeNode payload ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = dataService.beginTransaction(); + transaction.putConfigurationData( path, payload ); + Map createdConfigurationData = + transaction.getCreatedConfigurationData(); + CompositeNode createdNode = createdConfigurationData.get( path ); + + if( Objects.equal( payload, createdNode ) ) { + LOG.trace( "Post Configuration via Restconf: {}", path ); + return transaction.commit(); + } + + LOG.trace( "Post Configuration via Restconf was not executed because data already exists: {}", + path ); + return null; + } + + public Future> commitConfigurationDataPostBehindMountPoint( + final MountInstance mountPoint, final InstanceIdentifier path, final CompositeNode payload ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = mountPoint.beginTransaction(); + transaction.putConfigurationData( path, payload ); + Map createdConfigurationData = + transaction.getCreatedConfigurationData(); + CompositeNode createdNode = createdConfigurationData.get( path ); + + if( Objects.equal( payload, createdNode ) ) { + LOG.trace( "Post Configuration via Restconf: {}", path ); + return transaction.commit(); + } + + LOG.trace( "Post Configuration via Restconf was not executed because data already exists: {}", + path ); + return null; + } + + public Future> commitConfigurationDataDelete( final InstanceIdentifier path ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = dataService.beginTransaction(); + LOG.info( "Delete Configuration via Restconf: {}", path ); + transaction.removeConfigurationData( path ); + return transaction.commit(); + } + + public Future> commitConfigurationDataDeleteBehindMountPoint( + final MountInstance mountPoint, final InstanceIdentifier path ) { + this.checkPreconditions(); + + final DataModificationTransaction transaction = mountPoint.beginTransaction(); + LOG.info( "Delete Configuration via Restconf: {}", path ); + transaction.removeConfigurationData( path ); + return transaction.commit(); + } + + public void registerToListenDataChanges( final ListenerAdapter listener ) { + this.checkPreconditions(); + + if( listener.isListening() ) { + return; + } + + InstanceIdentifier path = listener.getPath(); + final ListenerRegistration registration = + dataService.registerDataChangeListener( path, listener ); + + listener.setRegistration( registration ); + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend deleted file mode 100644 index d3050061da..0000000000 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/BrokerFacade.xtend +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.sal.restconf.impl - -import javax.ws.rs.core.Response -import org.opendaylight.controller.md.sal.common.api.data.DataReader -import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession -import org.opendaylight.controller.sal.core.api.data.DataBrokerService -import org.opendaylight.controller.sal.core.api.mount.MountInstance -import org.opendaylight.controller.sal.rest.impl.RestconfProvider -import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter -import org.opendaylight.yangtools.yang.common.QName -import org.opendaylight.yangtools.yang.common.RpcResult -import org.opendaylight.yangtools.yang.data.api.CompositeNode -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier -import org.slf4j.LoggerFactory - -class BrokerFacade implements DataReader { - - - val static LOG = LoggerFactory.getLogger(BrokerFacade) - val static BrokerFacade INSTANCE = new BrokerFacade - - @Property - private ConsumerSession context; - - @Property - private DataBrokerService dataService; - - private new() { - if (INSTANCE !== null) { - throw new IllegalStateException("Already instantiated"); - } - } - - def static BrokerFacade getInstance() { - return INSTANCE - } - - private def void checkPreconditions() { - if (context === null || dataService === null) { - throw new ResponseException(Response.Status.SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG) - } - } - - override readConfigurationData(InstanceIdentifier path) { - checkPreconditions - LOG.trace("Read Configuration via Restconf: {}", path) - return dataService.readConfigurationData(path); - } - - def readConfigurationDataBehindMountPoint(MountInstance mountPoint, InstanceIdentifier path) { - checkPreconditions - LOG.trace("Read Configuration via Restconf: {}", path) - return mountPoint.readConfigurationData(path); - } - - override readOperationalData(InstanceIdentifier path) { - checkPreconditions - LOG.trace("Read Operational via Restconf: {}", path) - return dataService.readOperationalData(path); - } - - def readOperationalDataBehindMountPoint(MountInstance mountPoint, InstanceIdentifier path) { - checkPreconditions - LOG.trace("Read Operational via Restconf: {}", path) - return mountPoint.readOperationalData(path); - } - - def RpcResult invokeRpc(QName type, CompositeNode payload) { - checkPreconditions - val future = context.rpc(type, payload); - return future.get; - } - - def commitConfigurationDataPut(InstanceIdentifier path, CompositeNode payload) { - checkPreconditions - val transaction = dataService.beginTransaction; - LOG.trace("Put Configuration via Restconf: {}", path) - transaction.putConfigurationData(path, payload); - return transaction.commit - } - - def commitConfigurationDataPutBehindMountPoint(MountInstance mountPoint, InstanceIdentifier path, CompositeNode payload) { - checkPreconditions - val transaction = mountPoint.beginTransaction; - LOG.trace("Put Configuration via Restconf: {}", path) - transaction.putConfigurationData(path, payload); - return transaction.commit - } - - def commitConfigurationDataPost(InstanceIdentifier path, CompositeNode payload) { - checkPreconditions - val transaction = dataService.beginTransaction; - transaction.putConfigurationData(path, payload); - if (payload == transaction.createdConfigurationData.get(path)) { - LOG.trace("Post Configuration via Restconf: {}", path) - return transaction.commit - } - LOG.trace("Post Configuration via Restconf was not executed because data already exists: {}", path) - return null; - } - - def commitConfigurationDataPostBehindMountPoint(MountInstance mountPoint, InstanceIdentifier path, CompositeNode payload) { - checkPreconditions - val transaction = mountPoint.beginTransaction; - transaction.putConfigurationData(path, payload); - if (payload == transaction.createdConfigurationData.get(path)) { - LOG.trace("Post Configuration via Restconf: {}", path) - return transaction.commit - } - LOG.trace("Post Configuration via Restconf was not executed because data already exists: {}", path) - return null; - } - - def commitConfigurationDataDelete(InstanceIdentifier path) { - checkPreconditions - val transaction = dataService.beginTransaction; - LOG.info("Delete Configuration via Restconf: {}", path) - transaction.removeConfigurationData(path) - return transaction.commit - } - - def commitConfigurationDataDeleteBehindMountPoint(MountInstance mountPoint, InstanceIdentifier path) { - checkPreconditions - val transaction = mountPoint.beginTransaction; - LOG.info("Delete Configuration via Restconf: {}", path) - transaction.removeConfigurationData(path) - return transaction.commit - } - - def registerToListenDataChanges(ListenerAdapter listener) { - checkPreconditions - if (listener.listening) { - return; - } - val registration = dataService.registerDataChangeListener(listener.path, listener) - listener.setRegistration(registration) - } - -} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java new file mode 100644 index 0000000000..1c076d1e2e --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.java @@ -0,0 +1,912 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.restconf.impl; + +import com.google.common.base.Function; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.BiMap; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.core.Response.Status; + +import org.opendaylight.controller.sal.core.api.mount.MountInstance; +import org.opendaylight.controller.sal.core.api.mount.MountService; +import org.opendaylight.controller.sal.rest.impl.RestUtil; +import org.opendaylight.controller.sal.rest.impl.RestconfProvider; +import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode; +import org.opendaylight.controller.sal.restconf.impl.ResponseException; +import org.opendaylight.controller.sal.restconf.impl.RestCodec; +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument; +import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; +import org.opendaylight.yangtools.yang.model.api.ChoiceNode; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaContextListener; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ControllerContext implements SchemaContextListener { + private final static Logger LOG = LoggerFactory.getLogger( ControllerContext.class ); + + private final static ControllerContext INSTANCE = new ControllerContext(); + + private final static String NULL_VALUE = "null"; + + private final static String MOUNT_MODULE = "yang-ext"; + + private final static String MOUNT_NODE = "mount"; + + public final static String MOUNT = "yang-ext:mount"; + + private final static String URI_ENCODING_CHAR_SET = "ISO-8859-1"; + + private final BiMap uriToModuleName = HashBiMap. create(); + + private final Map moduleNameToUri = uriToModuleName.inverse(); + + private final Map qnameToRpc = new ConcurrentHashMap<>(); + + private volatile SchemaContext globalSchema; + private volatile MountService mountService; + + public void setGlobalSchema( final SchemaContext globalSchema ) { + this.globalSchema = globalSchema; + } + + public void setMountService( final MountService mountService ) { + this.mountService = mountService; + } + + private ControllerContext() { + } + + public static ControllerContext getInstance() { + return ControllerContext.INSTANCE; + } + + private void checkPreconditions() { + if( globalSchema == null ) { + throw new ResponseException( Status.SERVICE_UNAVAILABLE, RestconfProvider.NOT_INITALIZED_MSG ); + } + } + + public void setSchemas( final SchemaContext schemas ) { + this.onGlobalContextUpdated( schemas ); + } + + public InstanceIdWithSchemaNode toInstanceIdentifier( final String restconfInstance ) { + return this.toIdentifier( restconfInstance, false ); + } + + public InstanceIdWithSchemaNode toMountPointIdentifier( final String restconfInstance ) { + return this.toIdentifier( restconfInstance, true ); + } + + private InstanceIdWithSchemaNode toIdentifier( final String restconfInstance, + final boolean toMountPointIdentifier ) { + this.checkPreconditions(); + + Iterable split = Splitter.on( "/" ).split( restconfInstance ); + final ArrayList encodedPathArgs = Lists. newArrayList( split ); + final List pathArgs = this.urlPathArgsDecode( encodedPathArgs ); + this.omitFirstAndLastEmptyString( pathArgs ); + if( pathArgs.isEmpty() ) { + return null; + } + + String first = pathArgs.iterator().next(); + final String startModule = ControllerContext.toModuleName( first ); + if( startModule == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "First node in URI has to be in format \"moduleName:nodeName\"" ); + } + + InstanceIdentifierBuilder builder = InstanceIdentifier.builder(); + Module latestModule = this.getLatestModule( globalSchema, startModule ); + InstanceIdWithSchemaNode iiWithSchemaNode = this.collectPathArguments( builder, pathArgs, + latestModule, null, toMountPointIdentifier ); + + if( iiWithSchemaNode == null ) { + throw new ResponseException( Status.BAD_REQUEST, "URI has bad format" ); + } + + return iiWithSchemaNode; + } + + private List omitFirstAndLastEmptyString( final List list ) { + if( list.isEmpty() ) { + return list; + } + + String head = list.iterator().next(); + if( head.isEmpty() ) { + list.remove( 0 ); + } + + if( list.isEmpty() ) { + return list; + } + + String last = list.get( list.size() - 1 ); + if( last.isEmpty() ) { + list.remove( list.size() - 1 ); + } + + return list; + } + + private Module getLatestModule( final SchemaContext schema, final String moduleName ) { + Preconditions.checkArgument( schema != null ); + Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() ); + + Predicate filter = new Predicate() { + @Override + public boolean apply( Module m ) { + return Objects.equal( m.getName(), moduleName ); + } + }; + + Iterable modules = Iterables.filter( schema.getModules(), filter ); + return this.filterLatestModule( modules ); + } + + private Module filterLatestModule( final Iterable modules ) { + Module latestModule = modules.iterator().hasNext() ? modules.iterator().next() : null; + for( final Module module : modules ) { + if( module.getRevision().after( latestModule.getRevision() ) ) { + latestModule = module; + } + } + return latestModule; + } + + public Module findModuleByName( final String moduleName ) { + this.checkPreconditions(); + Preconditions.checkArgument( moduleName != null && !moduleName.isEmpty() ); + return this.getLatestModule( globalSchema, moduleName ); + } + + public Module findModuleByName( final MountInstance mountPoint, final String moduleName ) { + Preconditions.checkArgument( moduleName != null && mountPoint != null ); + + final SchemaContext mountPointSchema = mountPoint.getSchemaContext(); + return mountPointSchema == null ? null : this.getLatestModule( mountPointSchema, moduleName ); + } + + public Module findModuleByNamespace( final URI namespace ) { + this.checkPreconditions(); + Preconditions.checkArgument( namespace != null ); + + final Set moduleSchemas = globalSchema.findModuleByNamespace( namespace ); + return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas ); + } + + public Module findModuleByNamespace( final MountInstance mountPoint, final URI namespace ) { + Preconditions.checkArgument( namespace != null && mountPoint != null ); + + final SchemaContext mountPointSchema = mountPoint.getSchemaContext(); + Set moduleSchemas = mountPointSchema == null ? null : + mountPointSchema.findModuleByNamespace( namespace ); + return moduleSchemas == null ? null : this.filterLatestModule( moduleSchemas ); + } + + public Module findModuleByNameAndRevision( final QName module ) { + this.checkPreconditions(); + Preconditions.checkArgument( module != null && module.getLocalName() != null && + module.getRevision() != null ); + + return globalSchema.findModuleByName( module.getLocalName(), module.getRevision() ); + } + + public Module findModuleByNameAndRevision( final MountInstance mountPoint, final QName module ) { + this.checkPreconditions(); + Preconditions.checkArgument( module != null && module.getLocalName() != null && + module.getRevision() != null && mountPoint != null ); + + SchemaContext schemaContext = mountPoint.getSchemaContext(); + return schemaContext == null ? null : + schemaContext.findModuleByName( module.getLocalName(), module.getRevision() ); + } + + public DataNodeContainer getDataNodeContainerFor( final InstanceIdentifier path ) { + this.checkPreconditions(); + + final List elements = path.getPath(); + PathArgument head = elements.iterator().next(); + final QName startQName = head.getNodeType(); + final Module initialModule = globalSchema.findModuleByNamespaceAndRevision( + startQName.getNamespace(), startQName.getRevision() ); + DataNodeContainer node = initialModule; + for( final PathArgument element : elements ) { + QName _nodeType = element.getNodeType(); + final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType ); + if( potentialNode == null || !this.isListOrContainer( potentialNode ) ) { + return null; + } + node = (DataNodeContainer) potentialNode; + } + + return node; + } + + public String toFullRestconfIdentifier( final InstanceIdentifier path ) { + this.checkPreconditions(); + + final List elements = path.getPath(); + final StringBuilder builder = new StringBuilder(); + PathArgument head = elements.iterator().next(); + final QName startQName = head.getNodeType(); + final Module initialModule = globalSchema.findModuleByNamespaceAndRevision( + startQName.getNamespace(), startQName.getRevision() ); + DataNodeContainer node = initialModule; + for( final PathArgument element : elements ) { + QName _nodeType = element.getNodeType(); + final DataSchemaNode potentialNode = ControllerContext.childByQName( node, _nodeType ); + if( !this.isListOrContainer( potentialNode ) ) { + return null; + } + node = ((DataNodeContainer) potentialNode); + builder.append( this.convertToRestconfIdentifier( element, node ) ); + } + + return builder.toString(); + } + + public String findModuleNameByNamespace( final URI namespace ) { + this.checkPreconditions(); + + String moduleName = this.uriToModuleName.get( namespace ); + if( moduleName == null ) { + final Module module = this.findModuleByNamespace( namespace ); + if( module != null ) { + moduleName = module.getName(); + this.uriToModuleName.put( namespace, moduleName ); + } + } + + return moduleName; + } + + public String findModuleNameByNamespace( final MountInstance mountPoint, final URI namespace ) { + final Module module = this.findModuleByNamespace( mountPoint, namespace ); + return module == null ? null : module.getName(); + } + + public URI findNamespaceByModuleName( final String moduleName ) { + URI namespace = this.moduleNameToUri.get( moduleName ); + if( namespace == null ) { + Module module = this.findModuleByName( moduleName ); + if( module != null ) { + URI _namespace = module.getNamespace(); + namespace = _namespace; + this.uriToModuleName.put( namespace, moduleName ); + } + } + return namespace; + } + + public URI findNamespaceByModuleName( final MountInstance mountPoint, final String moduleName ) { + final Module module = this.findModuleByName( mountPoint, moduleName ); + return module == null ? null : module.getNamespace(); + } + + public Set getAllModules( final MountInstance mountPoint ) { + this.checkPreconditions(); + + SchemaContext schemaContext = mountPoint == null ? null : mountPoint.getSchemaContext(); + return schemaContext == null ? null : schemaContext.getModules(); + } + + public Set getAllModules() { + this.checkPreconditions(); + return globalSchema.getModules(); + } + + public CharSequence toRestconfIdentifier( final QName qname ) { + this.checkPreconditions(); + + String module = this.uriToModuleName.get( qname.getNamespace() ); + if( module == null ) { + final Module moduleSchema = globalSchema.findModuleByNamespaceAndRevision( + qname.getNamespace(), qname.getRevision() ); + if( moduleSchema == null ) { + return null; + } + + this.uriToModuleName.put( qname.getNamespace(), moduleSchema.getName() ); + module = moduleSchema.getName(); + } + + StringBuilder builder = new StringBuilder(); + builder.append( module ); + builder.append( ":" ); + builder.append( qname.getLocalName() ); + return builder.toString(); + } + + public CharSequence toRestconfIdentifier( final MountInstance mountPoint, final QName qname ) { + if( mountPoint == null ) { + return null; + } + + SchemaContext schemaContext = mountPoint.getSchemaContext(); + + final Module moduleSchema = schemaContext.findModuleByNamespaceAndRevision( + qname.getNamespace(), qname.getRevision() ); + if( moduleSchema == null ) { + return null; + } + + StringBuilder builder = new StringBuilder(); + builder.append( moduleSchema.getName() ); + builder.append( ":" ); + builder.append( qname.getLocalName() ); + return builder.toString(); + } + + private static DataSchemaNode childByQName( final ChoiceNode container, final QName name ) { + for( final ChoiceCaseNode caze : container.getCases() ) { + final DataSchemaNode ret = ControllerContext.childByQName( caze, name ); + if( ret != null ) { + return ret; + } + } + + return null; + } + + private static DataSchemaNode childByQName( final ChoiceCaseNode container, final QName name ) { + return container.getDataChildByName( name ); + } + + private static DataSchemaNode childByQName( final ContainerSchemaNode container, final QName name ) { + return ControllerContext.dataNodeChildByQName( container, name ); + } + + private static DataSchemaNode childByQName( final ListSchemaNode container, final QName name ) { + return ControllerContext.dataNodeChildByQName( container, name ); + } + + private static DataSchemaNode childByQName( final Module container, final QName name ) { + return ControllerContext.dataNodeChildByQName( container, name ); + } + + private static DataSchemaNode childByQName( final DataSchemaNode container, final QName name ) { + return null; + } + + private static DataSchemaNode dataNodeChildByQName( final DataNodeContainer container, final QName name ) { + DataSchemaNode ret = container.getDataChildByName( name ); + if( ret == null ) { + for( final DataSchemaNode node : container.getChildNodes() ) { + if( (node instanceof ChoiceCaseNode) ) { + final ChoiceCaseNode caseNode = ((ChoiceCaseNode) node); + DataSchemaNode childByQName = ControllerContext.childByQName( caseNode, name ); + if( childByQName != null ) { + return childByQName; + } + } + } + } + return ret; + } + + private String toUriString( final Object object ) throws UnsupportedEncodingException { + return object == null ? "" : + URLEncoder.encode( object.toString(), ControllerContext.URI_ENCODING_CHAR_SET ); + } + + private InstanceIdWithSchemaNode collectPathArguments( final InstanceIdentifierBuilder builder, + final List strings, final DataNodeContainer parentNode, final MountInstance mountPoint, + final boolean returnJustMountPoint ) { + Preconditions.> checkNotNull( strings ); + + if( parentNode == null ) { + return null; + } + + if( strings.isEmpty() ) { + return new InstanceIdWithSchemaNode( builder.toInstance(), + ((DataSchemaNode) parentNode), mountPoint ); + } + + String head = strings.iterator().next(); + final String nodeName = this.toNodeName( head ); + final String moduleName = ControllerContext.toModuleName( head ); + + DataSchemaNode targetNode = null; + if( !Strings.isNullOrEmpty( moduleName ) ) { + if( Objects.equal( moduleName, ControllerContext.MOUNT_MODULE ) && + Objects.equal( nodeName, ControllerContext.MOUNT_NODE ) ) { + if( mountPoint != null ) { + throw new ResponseException( Status.BAD_REQUEST, + "Restconf supports just one mount point in URI." ); + } + + if( mountService == null ) { + throw new ResponseException( Status.SERVICE_UNAVAILABLE, + "MountService was not found. Finding behind mount points does not work." ); + } + + final InstanceIdentifier partialPath = builder.toInstance(); + final MountInstance mount = mountService.getMountPoint( partialPath ); + if( mount == null ) { + LOG.debug( "Instance identifier to missing mount point: {}", partialPath ); + throw new ResponseException( Status.BAD_REQUEST, + "Mount point does not exist." ); + } + + final SchemaContext mountPointSchema = mount.getSchemaContext(); + if( mountPointSchema == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "Mount point does not contain any schema with modules." ); + } + + if( returnJustMountPoint ) { + InstanceIdentifier instance = InstanceIdentifier.builder().toInstance(); + return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount ); + } + + if( strings.size() == 1 ) { + InstanceIdentifier instance = InstanceIdentifier.builder().toInstance(); + return new InstanceIdWithSchemaNode( instance, mountPointSchema, mount ); + } + + final String moduleNameBehindMountPoint = toModuleName( strings.get( 1 ) ); + if( moduleNameBehindMountPoint == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "First node after mount point in URI has to be in format \"moduleName:nodeName\"" ); + } + + final Module moduleBehindMountPoint = this.getLatestModule( mountPointSchema, + moduleNameBehindMountPoint ); + if( moduleBehindMountPoint == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. \"" + moduleName + + "\" module does not exist in mount point." ); + } + + List subList = strings.subList( 1, strings.size() ); + return this.collectPathArguments( InstanceIdentifier.builder(), subList, moduleBehindMountPoint, + mount, returnJustMountPoint ); + } + + Module module = null; + if( mountPoint == null ) { + module = this.getLatestModule( globalSchema, moduleName ); + if( module == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. \"" + moduleName + "\" module does not exist." ); + } + } + else { + SchemaContext schemaContext = mountPoint.getSchemaContext(); + module = schemaContext == null ? null : + this.getLatestModule( schemaContext, moduleName ); + if( module == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. \"" + moduleName + + "\" module does not exist in mount point." ); + } + } + + targetNode = this.findInstanceDataChildByNameAndNamespace( + parentNode, nodeName, module.getNamespace() );; + if( targetNode == null ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. Possible reasons:\n" + + "1. \"" + head + "\" was not found in parent data node.\n" + + "2. \"" + head + "\" is behind mount point. Then it should be in format \"/" + + MOUNT + "/" + head + "\"." ); + } + } else { + final List potentialSchemaNodes = + this.findInstanceDataChildrenByName( parentNode, nodeName ); + if( potentialSchemaNodes.size() > 1 ) { + final StringBuilder strBuilder = new StringBuilder(); + for( final DataSchemaNode potentialNodeSchema : potentialSchemaNodes ) { + strBuilder.append( " " ) + .append( potentialNodeSchema.getQName().getNamespace() ) + .append( "\n" ); + } + + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. Node \"" + nodeName + + "\" is added as augment from more than one module. " + + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." + + "\nThe node is added as augment from modules with namespaces:\n" + + strBuilder.toString() ); + } + + if( potentialSchemaNodes.isEmpty() ) { + throw new ResponseException( Status.BAD_REQUEST, "URI has bad format. \"" + nodeName + + "\" was not found in parent data node.\n" ); + } + + targetNode = potentialSchemaNodes.iterator().next(); + } + + if( !this.isListOrContainer( targetNode ) ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. Node \"" + head + + "\" must be Container or List yang type." ); + } + + int consumed = 1; + if( (targetNode instanceof ListSchemaNode) ) { + final ListSchemaNode listNode = ((ListSchemaNode) targetNode); + final int keysSize = listNode.getKeyDefinition().size(); + if( (strings.size() - consumed) < keysSize ) { + throw new ResponseException( Status.BAD_REQUEST, "Missing key for list \"" + + listNode.getQName().getLocalName() + "\"." ); + } + + final List uriKeyValues = strings.subList( consumed, consumed + keysSize ); + final HashMap keyValues = new HashMap(); + int i = 0; + for( final QName key : listNode.getKeyDefinition() ) { + { + final String uriKeyValue = uriKeyValues.get( i ); + if( uriKeyValue.equals( NULL_VALUE ) ) { + throw new ResponseException( Status.BAD_REQUEST, + "URI has bad format. List \"" + listNode.getQName().getLocalName() + + "\" cannot contain \"null\" value as a key." ); + } + + this.addKeyValue( keyValues, listNode.getDataChildByName( key ), + uriKeyValue, mountPoint ); + i++; + } + } + + consumed = consumed + i; + builder.nodeWithKey( targetNode.getQName(), keyValues ); + } + else { + builder.node( targetNode.getQName() ); + } + + if( (targetNode instanceof DataNodeContainer) ) { + final List remaining = strings.subList( consumed, strings.size() ); + return this.collectPathArguments( builder, remaining, + ((DataNodeContainer) targetNode), mountPoint, returnJustMountPoint ); + } + + return new InstanceIdWithSchemaNode( builder.toInstance(), targetNode, mountPoint ); + } + + public DataSchemaNode findInstanceDataChildByNameAndNamespace( final DataNodeContainer container, + final String name, final URI namespace ) { + Preconditions. checkNotNull( namespace ); + + final List potentialSchemaNodes = this.findInstanceDataChildrenByName( container, name ); + + Predicate filter = new Predicate() { + @Override + public boolean apply( DataSchemaNode node ) { + return Objects.equal( node.getQName().getNamespace(), namespace ); + } + }; + + Iterable result = Iterables.filter( potentialSchemaNodes, filter ); + return Iterables.getFirst( result, null ); + } + + public List findInstanceDataChildrenByName( final DataNodeContainer container, + final String name ) { + Preconditions. checkNotNull( container ); + Preconditions. checkNotNull( name ); + + List instantiatedDataNodeContainers = new ArrayList(); + this.collectInstanceDataNodeContainers( instantiatedDataNodeContainers, container, name ); + return instantiatedDataNodeContainers; + } + + private void collectInstanceDataNodeContainers( final List potentialSchemaNodes, + final DataNodeContainer container, final String name ) { + + Set childNodes = container.getChildNodes(); + + Predicate filter = new Predicate() { + @Override + public boolean apply( DataSchemaNode node ) { + return Objects.equal( node.getQName().getLocalName(), name ); + } + }; + + Iterable nodes = Iterables.filter( childNodes, filter ); + + // Can't combine this loop with the filter above because the filter is lazily-applied + // by Iterables.filter. + for( final DataSchemaNode potentialNode : nodes ) { + if( this.isInstantiatedDataSchema( potentialNode ) ) { + potentialSchemaNodes.add( potentialNode ); + } + } + + Iterable choiceNodes = Iterables. filter( container.getChildNodes(), + ChoiceNode.class ); + + final Function> choiceFunction = + new Function>() { + @Override + public Set apply( final ChoiceNode node ) { + return node.getCases(); + } + }; + + Iterable> map = Iterables.> transform( + choiceNodes, choiceFunction ); + + final Iterable allCases = Iterables. concat( map ); + for( final ChoiceCaseNode caze : allCases ) { + this.collectInstanceDataNodeContainers( potentialSchemaNodes, caze, name ); + } + } + + public boolean isInstantiatedDataSchema( final DataSchemaNode node ) { + return node instanceof LeafSchemaNode || node instanceof LeafListSchemaNode || + node instanceof ContainerSchemaNode || node instanceof ListSchemaNode; + } + + private void addKeyValue( final HashMap map, final DataSchemaNode node, + final String uriValue, final MountInstance mountPoint ) { + Preconditions. checkNotNull( uriValue ); + Preconditions.checkArgument( (node instanceof LeafSchemaNode) ); + + final String urlDecoded = urlPathArgDecode( uriValue ); + final TypeDefinition typedef = ((LeafSchemaNode) node).getType(); + Codec codec = RestCodec.from( typedef, mountPoint ); + + Object decoded = codec == null ? null : codec.deserialize( urlDecoded ); + String additionalInfo = ""; + if( decoded == null ) { + TypeDefinition baseType = RestUtil.resolveBaseTypeFrom( typedef ); + if( (baseType instanceof IdentityrefTypeDefinition) ) { + decoded = this.toQName( urlDecoded ); + additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name."; + } + } + + if( decoded == null ) { + throw new ResponseException( Status.BAD_REQUEST, uriValue + " from URI can\'t be resolved. " + + additionalInfo ); + } + + map.put( node.getQName(), decoded ); + } + + private static String toModuleName( final String str ) { + Preconditions. checkNotNull( str ); + if( str.contains( ":" ) ) { + final String[] args = str.split( ":" ); + if( args.length == 2 ) { + return args[0]; + } + } + return null; + } + + private String toNodeName( final String str ) { + if( str.contains( ":" ) ) { + final String[] args = str.split( ":" ); + if( args.length == 2 ) { + return args[1]; + } + } + return str; + } + + private QName toQName( final String name ) { + final String module = toModuleName( name ); + final String node = this.toNodeName( name ); + Set modules = globalSchema.getModules(); + + final Comparator comparator = new Comparator() { + @Override + public int compare( final Module o1, final Module o2 ) { + return o1.getRevision().compareTo( o2.getRevision() ); + } + }; + + List sorted = new ArrayList( modules ); + Collections. sort( new ArrayList( modules ), comparator ); + + final Function transform = new Function() { + @Override + public QName apply( final Module m ) { + return QName.create( m.getNamespace(), m.getRevision(), m.getName() ); + } + }; + + final Predicate findFirst = new Predicate() { + @Override + public boolean apply( final QName qn ) { + return Objects.equal( module, qn.getLocalName() ); + } + }; + + Optional namespace = FluentIterable.from( sorted ) + .transform( transform ) + .firstMatch( findFirst ); + return namespace.isPresent() ? QName.create( namespace.get(), node ) : null; + } + + private boolean isListOrContainer( final DataSchemaNode node ) { + return node instanceof ListSchemaNode || node instanceof ContainerSchemaNode; + } + + public RpcDefinition getRpcDefinition( final String name ) { + final QName validName = this.toQName( name ); + return validName == null ? null : this.qnameToRpc.get( validName ); + } + + @Override + public void onGlobalContextUpdated( final SchemaContext context ) { + if( context != null ) { + this.qnameToRpc.clear(); + this.setGlobalSchema( context ); + Set _operations = context.getOperations(); + for( final RpcDefinition operation : _operations ) { + { + this.qnameToRpc.put( operation.getQName(), operation ); + } + } + } + } + + public List urlPathArgsDecode( final List strings ) { + try { + List decodedPathArgs = new ArrayList(); + for( final String pathArg : strings ) { + String _decode = URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET ); + decodedPathArgs.add( _decode ); + } + return decodedPathArgs; + } + catch( UnsupportedEncodingException e ) { + throw new ResponseException( Status.BAD_REQUEST, + "Invalid URL path '" + strings + "': " + e.getMessage() ); + } + } + + public String urlPathArgDecode( final String pathArg ) { + if( pathArg != null ) { + try { + return URLDecoder.decode( pathArg, URI_ENCODING_CHAR_SET ); + } + catch( UnsupportedEncodingException e ) { + throw new ResponseException( Status.BAD_REQUEST, + "Invalid URL path arg '" + pathArg + "': " + e.getMessage() ); + } + } + + return null; + } + + private CharSequence convertToRestconfIdentifier( final PathArgument argument, + final DataNodeContainer node ) { + if( argument instanceof NodeIdentifier && node instanceof ContainerSchemaNode ) { + return convertToRestconfIdentifier( (NodeIdentifier) argument, (ContainerSchemaNode) node ); + } + else if( argument instanceof NodeIdentifierWithPredicates && node instanceof ListSchemaNode ) { + return convertToRestconfIdentifier( (NodeIdentifierWithPredicates) argument, (ListSchemaNode) node ); + } + else if( argument != null && node != null ) { + throw new IllegalArgumentException( + "Conversion of generic path argument is not supported" ); + } + else { + throw new IllegalArgumentException( "Unhandled parameter types: " + + Arrays. asList( argument, node ).toString() ); + } + } + + private CharSequence convertToRestconfIdentifier( final NodeIdentifier argument, + final ContainerSchemaNode node ) { + StringBuilder builder = new StringBuilder(); + builder.append( "/" ); + QName nodeType = argument.getNodeType(); + builder.append( this.toRestconfIdentifier( nodeType ) ); + return builder.toString(); + } + + private CharSequence convertToRestconfIdentifier( final NodeIdentifierWithPredicates argument, + final ListSchemaNode node ) { + QName nodeType = argument.getNodeType(); + final CharSequence nodeIdentifier = this.toRestconfIdentifier( nodeType ); + final Map keyValues = argument.getKeyValues(); + + StringBuilder builder = new StringBuilder(); + builder.append( "/" ); + builder.append( nodeIdentifier ); + builder.append( "/" ); + + List keyDefinition = node.getKeyDefinition(); + boolean hasElements = false; + for( final QName key : keyDefinition ) { + if( !hasElements ) { + hasElements = true; + } + else { + builder.append( "/" ); + } + + try { + builder.append( this.toUriString( keyValues.get( key ) ) ); + } catch( UnsupportedEncodingException e ) { + LOG.error( "Error parsing URI: " + keyValues.get( key ), e ); + return null; + } + } + + return builder.toString(); + } + + private static DataSchemaNode childByQName( final Object container, final QName name ) { + if( container instanceof ChoiceCaseNode ) { + return childByQName( (ChoiceCaseNode) container, name ); + } + else if( container instanceof ChoiceNode ) { + return childByQName( (ChoiceNode) container, name ); + } + else if( container instanceof ContainerSchemaNode ) { + return childByQName( (ContainerSchemaNode) container, name ); + } + else if( container instanceof ListSchemaNode ) { + return childByQName( (ListSchemaNode) container, name ); + } + else if( container instanceof DataSchemaNode ) { + return childByQName( (DataSchemaNode) container, name ); + } + else if( container instanceof Module ) { + return childByQName( (Module) container, name ); + } + else { + throw new IllegalArgumentException( "Unhandled parameter types: " + + Arrays. asList( container, name ).toString() ); + } + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend deleted file mode 100644 index cb02fc89bf..0000000000 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ControllerContext.xtend +++ /dev/null @@ -1,637 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.sal.restconf.impl - -import com.google.common.base.Preconditions -import com.google.common.base.Splitter -import com.google.common.collect.BiMap -import com.google.common.collect.FluentIterable -import com.google.common.collect.HashBiMap -import com.google.common.collect.Lists -import java.net.URI -import java.net.URLDecoder -import java.net.URLEncoder -import java.util.ArrayList -import java.util.HashMap -import java.util.List -import java.util.Map -import java.util.concurrent.ConcurrentHashMap -import org.opendaylight.controller.sal.core.api.mount.MountInstance -import org.opendaylight.controller.sal.core.api.mount.MountService -import org.opendaylight.controller.sal.rest.impl.RestUtil -import org.opendaylight.controller.sal.rest.impl.RestconfProvider -import org.opendaylight.yangtools.yang.common.QName -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifier -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.NodeIdentifierWithPredicates -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.PathArgument -import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec -import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode -import org.opendaylight.yangtools.yang.model.api.ChoiceNode -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode -import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode -import org.opendaylight.yangtools.yang.model.api.Module -import org.opendaylight.yangtools.yang.model.api.RpcDefinition -import org.opendaylight.yangtools.yang.model.api.SchemaContext -import org.opendaylight.yangtools.yang.model.api.SchemaContextListener -import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition -import org.slf4j.LoggerFactory - -import static com.google.common.base.Preconditions.* -import static javax.ws.rs.core.Response.Status.* - -class ControllerContext implements SchemaContextListener { - val static LOG = LoggerFactory.getLogger(ControllerContext) - val static ControllerContext INSTANCE = new ControllerContext - val static NULL_VALUE = "null" - val static MOUNT_MODULE = "yang-ext" - val static MOUNT_NODE = "mount" - public val static MOUNT = "yang-ext:mount" - val static URI_ENCODING_CHAR_SET = "ISO-8859-1" - val static URI_SLASH_PLACEHOLDER = "%2F"; - - @Property - var SchemaContext globalSchema; - - @Property - var MountService mountService; - - private val BiMap uriToModuleName = HashBiMap.create(); - private val Map moduleNameToUri = uriToModuleName.inverse(); - private val Map qnameToRpc = new ConcurrentHashMap(); - - private new() { - if (INSTANCE !== null) { - throw new IllegalStateException("Already instantiated"); - } - } - - static def getInstance() { - return INSTANCE - } - - private def void checkPreconditions() { - if (globalSchema === null) { - throw new ResponseException(SERVICE_UNAVAILABLE, RestconfProvider::NOT_INITALIZED_MSG) - } - } - - def setSchemas(SchemaContext schemas) { - onGlobalContextUpdated(schemas) - } - - def InstanceIdWithSchemaNode toInstanceIdentifier(String restconfInstance) { - return restconfInstance.toIdentifier(false) - } - - def InstanceIdWithSchemaNode toMountPointIdentifier(String restconfInstance) { - return restconfInstance.toIdentifier(true) - } - - private def InstanceIdWithSchemaNode toIdentifier(String restconfInstance, boolean toMountPointIdentifier) { - checkPreconditions - val encodedPathArgs = Lists.newArrayList(Splitter.on("/").split(restconfInstance)) - val pathArgs = urlPathArgsDecode(encodedPathArgs) - pathArgs.omitFirstAndLastEmptyString - if (pathArgs.empty) { - return null; - } - val startModule = pathArgs.head.toModuleName(); - if (startModule === null) { - throw new ResponseException(BAD_REQUEST, "First node in URI has to be in format \"moduleName:nodeName\"") - } - var InstanceIdWithSchemaNode iiWithSchemaNode = null; - if (toMountPointIdentifier) { - iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, - globalSchema.getLatestModule(startModule), null, true); - } else { - iiWithSchemaNode = collectPathArguments(InstanceIdentifier.builder(), pathArgs, - globalSchema.getLatestModule(startModule), null, false); - } - if (iiWithSchemaNode === null) { - throw new ResponseException(BAD_REQUEST, "URI has bad format") - } - return iiWithSchemaNode - } - - private def omitFirstAndLastEmptyString(List list) { - if (list.empty) { - return list; - } - if (list.head.empty) { - list.remove(0) - } - if (list.empty) { - return list; - } - if (list.last.empty) { - list.remove(list.indexOf(list.last)) - } - return list; - } - - private def getLatestModule(SchemaContext schema, String moduleName) { - checkArgument(schema !== null); - checkArgument(moduleName !== null && !moduleName.empty) - val modules = schema.modules.filter[m|m.name == moduleName] - return modules.filterLatestModule - } - - private def filterLatestModule(Iterable modules) { - var latestModule = modules.head - for (module : modules) { - if (module.revision.after(latestModule.revision)) { - latestModule = module - } - } - return latestModule - } - - def findModuleByName(String moduleName) { - checkPreconditions - checkArgument(moduleName !== null && !moduleName.empty) - return globalSchema.getLatestModule(moduleName) - } - - def findModuleByName(MountInstance mountPoint, String moduleName) { - checkArgument(moduleName !== null && mountPoint !== null) - val mountPointSchema = mountPoint.schemaContext; - return mountPointSchema?.getLatestModule(moduleName); - } - - def findModuleByNamespace(URI namespace) { - checkPreconditions - checkArgument(namespace !== null) - val moduleSchemas = globalSchema.findModuleByNamespace(namespace) - return moduleSchemas?.filterLatestModule - } - - def findModuleByNamespace(MountInstance mountPoint, URI namespace) { - checkArgument(namespace !== null && mountPoint !== null) - val mountPointSchema = mountPoint.schemaContext; - val moduleSchemas = mountPointSchema?.findModuleByNamespace(namespace) - return moduleSchemas?.filterLatestModule - } - - def findModuleByNameAndRevision(QName module) { - checkPreconditions - checkArgument(module !== null && module.localName !== null && module.revision !== null) - return globalSchema.findModuleByName(module.localName, module.revision) - } - - def findModuleByNameAndRevision(MountInstance mountPoint, QName module) { - checkPreconditions - checkArgument(module !== null && module.localName !== null && module.revision !== null && mountPoint !== null) - return mountPoint.schemaContext?.findModuleByName(module.localName, module.revision) - } - - def getDataNodeContainerFor(InstanceIdentifier path) { - checkPreconditions - val elements = path.path; - val startQName = elements.head.nodeType; - val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision) - var node = initialModule as DataNodeContainer; - for (element : elements) { - val potentialNode = node.childByQName(element.nodeType); - if (potentialNode === null || !potentialNode.listOrContainer) { - return null - } - node = potentialNode as DataNodeContainer - } - return node - } - - def String toFullRestconfIdentifier(InstanceIdentifier path) { - checkPreconditions - val elements = path.path; - val ret = new StringBuilder(); - val startQName = elements.head.nodeType; - val initialModule = globalSchema.findModuleByNamespaceAndRevision(startQName.namespace, startQName.revision) - var node = initialModule as DataNodeContainer; - for (element : elements) { - val potentialNode = node.childByQName(element.nodeType); - if (!potentialNode.listOrContainer) { - return null - } - node = potentialNode as DataNodeContainer - ret.append(element.convertToRestconfIdentifier(node)); - } - return ret.toString - } - - private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifier argument, ContainerSchemaNode node) { - '''/«argument.nodeType.toRestconfIdentifier()»''' - } - - private def dispatch CharSequence convertToRestconfIdentifier(NodeIdentifierWithPredicates argument, ListSchemaNode node) { - val nodeIdentifier = argument.nodeType.toRestconfIdentifier(); - val keyValues = argument.keyValues; - return '''/«nodeIdentifier»/«FOR key : node.keyDefinition SEPARATOR "/"»«keyValues.get(key).toUriString»«ENDFOR»''' - } - - private def dispatch CharSequence convertToRestconfIdentifier(PathArgument argument, DataNodeContainer node) { - throw new IllegalArgumentException("Conversion of generic path argument is not supported"); - } - - def findModuleNameByNamespace(URI namespace) { - checkPreconditions - var moduleName = uriToModuleName.get(namespace) - if (moduleName === null) { - val module = findModuleByNamespace(namespace) - if (module === null) return null - moduleName = module.name - uriToModuleName.put(namespace, moduleName) - } - return moduleName - } - - def findModuleNameByNamespace(MountInstance mountPoint, URI namespace) { - val module = mountPoint.findModuleByNamespace(namespace); - return module?.name - } - - def findNamespaceByModuleName(String moduleName) { - var namespace = moduleNameToUri.get(moduleName) - if (namespace === null) { - var module = findModuleByName(moduleName) - if(module === null) return null - namespace = module.namespace - uriToModuleName.put(namespace, moduleName) - } - return namespace - } - - def findNamespaceByModuleName(MountInstance mountPoint, String moduleName) { - val module = mountPoint.findModuleByName(moduleName) - return module?.namespace - } - - def getAllModules(MountInstance mountPoint) { - checkPreconditions - return mountPoint?.schemaContext?.modules - } - - def getAllModules() { - checkPreconditions - return globalSchema.modules - } - - def CharSequence toRestconfIdentifier(QName qname) { - checkPreconditions - var module = uriToModuleName.get(qname.namespace) - if (module === null) { - val moduleSchema = globalSchema.findModuleByNamespaceAndRevision(qname.namespace, qname.revision); - if(moduleSchema === null) return null - uriToModuleName.put(qname.namespace, moduleSchema.name) - module = moduleSchema.name; - } - return '''«module»:«qname.localName»'''; - } - - def CharSequence toRestconfIdentifier(MountInstance mountPoint, QName qname) { - val moduleSchema = mountPoint?.schemaContext.findModuleByNamespaceAndRevision(qname.namespace, qname.revision); - if(moduleSchema === null) return null - val module = moduleSchema.name; - return '''«module»:«qname.localName»'''; - } - - private static dispatch def DataSchemaNode childByQName(ChoiceNode container, QName name) { - for (caze : container.cases) { - val ret = caze.childByQName(name) - if (ret !== null) { - return ret; - } - } - return null; - } - - private static dispatch def DataSchemaNode childByQName(ChoiceCaseNode container, QName name) { - val ret = container.getDataChildByName(name); - return ret; - } - - private static dispatch def DataSchemaNode childByQName(ContainerSchemaNode container, QName name) { - return container.dataNodeChildByQName(name); - } - - private static dispatch def DataSchemaNode childByQName(ListSchemaNode container, QName name) { - return container.dataNodeChildByQName(name); - } - - private static dispatch def DataSchemaNode childByQName(Module container, QName name) { - return container.dataNodeChildByQName(name); - } - - private static dispatch def DataSchemaNode childByQName(DataSchemaNode container, QName name) { - return null; - } - - private static def DataSchemaNode dataNodeChildByQName(DataNodeContainer container, QName name) { - var ret = container.getDataChildByName(name); - if (ret === null) { - - // Find in Choice Cases - for (node : container.childNodes) { - if (node instanceof ChoiceCaseNode) { - val caseNode = (node as ChoiceCaseNode); - ret = caseNode.childByQName(name); - if (ret !== null) { - return ret; - } - } - } - } - return ret; - } - - private def toUriString(Object object) { - if(object === null) return ""; - return URLEncoder.encode(object.toString,URI_ENCODING_CHAR_SET) - } - - private def InstanceIdWithSchemaNode collectPathArguments(InstanceIdentifierBuilder builder, List strings, - DataNodeContainer parentNode, MountInstance mountPoint, boolean returnJustMountPoint) { - checkNotNull(strings) - if (parentNode === null) { - return null; - } - if (strings.empty) { - return new InstanceIdWithSchemaNode(builder.toInstance, parentNode as DataSchemaNode, mountPoint) - } - - val nodeName = strings.head.toNodeName - val moduleName = strings.head.toModuleName - var DataSchemaNode targetNode = null - if (!moduleName.nullOrEmpty) { - // if it is mount point - if (moduleName == MOUNT_MODULE && nodeName == MOUNT_NODE) { - if (mountPoint !== null) { - throw new ResponseException(BAD_REQUEST, "Restconf supports just one mount point in URI.") - } - - if (mountService === null) { - throw new ResponseException(SERVICE_UNAVAILABLE, "MountService was not found. " - + "Finding behind mount points does not work." - ) - } - - val partialPath = builder.toInstance; - val mount = mountService.getMountPoint(partialPath) - if (mount === null) { - LOG.debug("Instance identifier to missing mount point: {}", partialPath) - throw new ResponseException(BAD_REQUEST, "Mount point does not exist.") - } - - val mountPointSchema = mount.schemaContext; - if (mountPointSchema === null) { - throw new ResponseException(BAD_REQUEST, "Mount point does not contain any schema with modules.") - } - - if (returnJustMountPoint) { - return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount) - } - - if (strings.size == 1) { // any data node is not behind mount point - return new InstanceIdWithSchemaNode(InstanceIdentifier.builder().toInstance, mountPointSchema, mount) - } - - val moduleNameBehindMountPoint = strings.get(1).toModuleName() - if (moduleNameBehindMountPoint === null) { - throw new ResponseException(BAD_REQUEST, - "First node after mount point in URI has to be in format \"moduleName:nodeName\"") - } - - val moduleBehindMountPoint = mountPointSchema.getLatestModule(moduleNameBehindMountPoint) - if (moduleBehindMountPoint === null) { - throw new ResponseException(BAD_REQUEST, - "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.") - } - - return collectPathArguments(InstanceIdentifier.builder(), strings.subList(1, strings.size), - moduleBehindMountPoint, mount, returnJustMountPoint); - } - - var Module module = null; - if (mountPoint === null) { - module = globalSchema.getLatestModule(moduleName) - if (module === null) { - throw new ResponseException(BAD_REQUEST, - "URI has bad format. \"" + moduleName + "\" module does not exist.") - } - } else { - module = mountPoint.schemaContext?.getLatestModule(moduleName) - if (module === null) { - throw new ResponseException(BAD_REQUEST, - "URI has bad format. \"" + moduleName + "\" module does not exist in mount point.") - } - } - targetNode = parentNode.findInstanceDataChildByNameAndNamespace(nodeName, module.namespace) - if (targetNode === null) { - throw new ResponseException(BAD_REQUEST, "URI has bad format. Possible reasons:\n" + - "1. \"" + strings.head + "\" was not found in parent data node.\n" + - "2. \"" + strings.head + "\" is behind mount point. Then it should be in format \"/" + MOUNT + "/" + strings.head + "\".") - } - } else { // string without module name - val potentialSchemaNodes = parentNode.findInstanceDataChildrenByName(nodeName) - if (potentialSchemaNodes.size > 1) { - val StringBuilder namespacesOfPotentialModules = new StringBuilder; - for (potentialNodeSchema : potentialSchemaNodes) { - namespacesOfPotentialModules.append(" ").append(potentialNodeSchema.QName.namespace.toString).append("\n") - } - throw new ResponseException(BAD_REQUEST, "URI has bad format. Node \"" + nodeName + "\" is added as augment from more than one module. " - + "Therefore the node must have module name and it has to be in format \"moduleName:nodeName\"." - + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules) - } - targetNode = potentialSchemaNodes.head - if (targetNode === null) { - throw new ResponseException(BAD_REQUEST, "URI has bad format. \"" + nodeName + "\" was not found in parent data node.\n") - } - } - - if (!targetNode.isListOrContainer) { - throw new ResponseException(BAD_REQUEST,"URI has bad format. Node \"" + strings.head + "\" must be Container or List yang type.") - } - // Number of consumed elements - var consumed = 1; - if (targetNode instanceof ListSchemaNode) { - val listNode = targetNode as ListSchemaNode; - val keysSize = listNode.keyDefinition.size - - // every key has to be filled - if ((strings.length - consumed) < keysSize) { - throw new ResponseException(BAD_REQUEST,"Missing key for list \"" + listNode.QName.localName + "\".") - } - val uriKeyValues = strings.subList(consumed, consumed + keysSize); - val keyValues = new HashMap(); - var i = 0; - for (key : listNode.keyDefinition) { - val uriKeyValue = uriKeyValues.get(i); - - // key value cannot be NULL - if (uriKeyValue.equals(NULL_VALUE)) { - throw new ResponseException(BAD_REQUEST, "URI has bad format. List \"" + listNode.QName.localName - + "\" cannot contain \"null\" value as a key." - ) - } - keyValues.addKeyValue(listNode.getDataChildByName(key), uriKeyValue, mountPoint); - i = i + 1; - } - consumed = consumed + i; - builder.nodeWithKey(targetNode.QName, keyValues); - } else { - - // Only one instance of node is allowed - builder.node(targetNode.QName); - } - if (targetNode instanceof DataNodeContainer) { - val remaining = strings.subList(consumed, strings.length); - val result = builder.collectPathArguments(remaining, targetNode as DataNodeContainer, mountPoint, returnJustMountPoint); - return result - } - - return new InstanceIdWithSchemaNode(builder.toInstance, targetNode, mountPoint) - } - - def DataSchemaNode findInstanceDataChildByNameAndNamespace(DataNodeContainer container, - String name, URI namespace) { - Preconditions.checkNotNull(namespace) - val potentialSchemaNodes = container.findInstanceDataChildrenByName(name) - return potentialSchemaNodes.filter[n|n.QName.namespace == namespace].head - } - - def List findInstanceDataChildrenByName(DataNodeContainer container, String name) { - Preconditions.checkNotNull(container) - Preconditions.checkNotNull(name) - val instantiatedDataNodeContainers = new ArrayList - instantiatedDataNodeContainers.collectInstanceDataNodeContainers(container, name) - return instantiatedDataNodeContainers - } - - private def void collectInstanceDataNodeContainers(List potentialSchemaNodes, DataNodeContainer container, - String name) { - val nodes = container.childNodes.filter[n|n.QName.localName == name] - for (potentialNode : nodes) { - if (potentialNode.isInstantiatedDataSchema) { - potentialSchemaNodes.add(potentialNode) - } - } - val allCases = container.childNodes.filter(ChoiceNode).map[cases].flatten - for (caze : allCases) { - collectInstanceDataNodeContainers(potentialSchemaNodes, caze, name) - } - } - - def boolean isInstantiatedDataSchema(DataSchemaNode node) { - switch node { - LeafSchemaNode: return true - LeafListSchemaNode: return true - ContainerSchemaNode: return true - ListSchemaNode: return true - default: return false - } - } - - private def void addKeyValue(HashMap map, DataSchemaNode node, String uriValue, MountInstance mountPoint) { - checkNotNull(uriValue); - checkArgument(node instanceof LeafSchemaNode); - val urlDecoded = URLDecoder.decode(uriValue); - val typedef = (node as LeafSchemaNode).type; - - var decoded = RestCodec.from(typedef, mountPoint)?.deserialize(urlDecoded) - var additionalInfo = "" - if(decoded === null) { - var baseType = RestUtil.resolveBaseTypeFrom(typedef) - if(baseType instanceof IdentityrefTypeDefinition) { - decoded = toQName(urlDecoded) - additionalInfo = "For key which is of type identityref it should be in format module_name:identity_name." - } - } - if (decoded === null) { - throw new ResponseException(BAD_REQUEST, uriValue + " from URI can't be resolved. "+ additionalInfo ) - } - - map.put(node.QName, decoded); - } - - private static def String toModuleName(String str) { - checkNotNull(str) - if (str.contains(":")) { - val args = str.split(":"); - if (args.size === 2) { - return args.get(0); - } - } - return null; - } - - private def String toNodeName(String str) { - if (str.contains(":")) { - val args = str.split(":"); - if (args.size === 2) { - return args.get(1); - } - } - return str; - } - - private def QName toQName(String name) { - val module = name.toModuleName; - val node = name.toNodeName; - val namespace = FluentIterable.from(globalSchema.modules.sort[o1,o2 | o1.revision.compareTo(o2.revision)]) - .transform[QName.create(namespace,revision,it.name)].findFirst[module == localName] - if (namespace === null) { - return null - } - return QName.create(namespace, node); - } - - private def boolean isListOrContainer(DataSchemaNode node) { - return ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) - } - - def getRpcDefinition(String name) { - val validName = name.toQName - if (validName === null) { - return null - } - return qnameToRpc.get(validName) - } - - override onGlobalContextUpdated(SchemaContext context) { - if (context !== null) { - qnameToRpc.clear - this.globalSchema = context; - for (operation : context.operations) { - val qname = operation.QName; - qnameToRpc.put(qname, operation); - } - } - } - - - def urlPathArgsDecode(List strings) { - val List decodedPathArgs = new ArrayList(); - for (pathArg : strings) { - decodedPathArgs.add(URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET)) - } - return decodedPathArgs - } - - def urlPathArgDecode(String pathArg) { - if (pathArg !== null) { - return URLDecoder.decode(pathArg, URI_ENCODING_CHAR_SET) - } - return null - } - -} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ResponseException.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ResponseException.java index b08126b5de..007fb8eabf 100644 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ResponseException.java +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/ResponseException.java @@ -19,4 +19,9 @@ public class ResponseException extends WebApplicationException { public ResponseException(Status status, String msg) { super(Response.status(status).type(MediaType.TEXT_PLAIN_TYPE).entity(msg).build()); } + + public ResponseException(Throwable cause, String msg) { + super(cause, Response.status(Status.INTERNAL_SERVER_ERROR). + type(MediaType.TEXT_PLAIN_TYPE).entity(msg).build()); + } } diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java new file mode 100644 index 0000000000..2e198ec053 --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.java @@ -0,0 +1,1170 @@ +/** + * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.sal.restconf.impl; + +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import java.net.URI; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Future; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.UriBuilder; +import javax.ws.rs.core.UriInfo; + +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.core.api.mount.MountInstance; +import org.opendaylight.controller.sal.rest.api.RestconfService; +import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; +import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.ControllerContext; +import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO; +import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode; +import org.opendaylight.controller.sal.restconf.impl.NodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.ResponseException; +import org.opendaylight.controller.sal.restconf.impl.RestCodec; +import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper; +import org.opendaylight.controller.sal.restconf.impl.StructuredData; +import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.controller.sal.streams.listeners.Notificator; +import org.opendaylight.controller.sal.streams.websockets.WebSocketServer; +import org.opendaylight.yangtools.concepts.Codec; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder; +import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode; +import org.opendaylight.yangtools.yang.data.api.Node; +import org.opendaylight.yangtools.yang.data.api.SimpleNode; +import org.opendaylight.yangtools.yang.data.impl.NodeFactory; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; +import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; +import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.FeatureDefinition; +import org.opendaylight.yangtools.yang.model.api.GroupingDefinition; +import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.RpcDefinition; +import org.opendaylight.yangtools.yang.model.api.SchemaContext; +import org.opendaylight.yangtools.yang.model.api.SchemaPath; +import org.opendaylight.yangtools.yang.model.api.TypeDefinition; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.opendaylight.yangtools.yang.model.util.EmptyType; +import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder; +import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder; + +@SuppressWarnings("all") +public class RestconfImpl implements RestconfService { + private final static RestconfImpl INSTANCE = new RestconfImpl(); + + private final static String MOUNT_POINT_MODULE_NAME = "ietf-netconf"; + + private final static SimpleDateFormat REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + + private final static String RESTCONF_MODULE_DRAFT02_REVISION = "2013-10-19"; + + private final static String RESTCONF_MODULE_DRAFT02_NAME = "ietf-restconf"; + + private final static String RESTCONF_MODULE_DRAFT02_NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf"; + + private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE = "restconf"; + + private final static String RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE = "restconf"; + + private final static String RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE = "modules"; + + private final static String RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE = "module"; + + private final static String RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE = "streams"; + + private final static String RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE = "stream"; + + private final static String RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations"; + + private final static String SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote"; + + private final static String SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription"; + + private BrokerFacade broker; + + private ControllerContext controllerContext; + + public void setBroker(final BrokerFacade broker) { + this.broker = broker; + } + + public void setControllerContext(final ControllerContext controllerContext) { + this.controllerContext = controllerContext; + } + + private RestconfImpl() { + } + + public static RestconfImpl getInstance() { + return INSTANCE; + } + + @Override + public StructuredData getModules() { + final Module restconfModule = this.getRestconfModule(); + + final List> modulesAsData = new ArrayList>(); + final DataSchemaNode moduleSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + + Set allModules = this.controllerContext.getAllModules(); + for (final Module module : allModules) { + CompositeNode moduleCompositeNode = this.toModuleCompositeNode(module, moduleSchemaNode); + modulesAsData.add(moduleCompositeNode); + } + + final DataSchemaNode modulesSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + QName qName = modulesSchemaNode.getQName(); + final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData); + return new StructuredData(modulesNode, modulesSchemaNode, null); + } + + @Override + public StructuredData getAvailableStreams() { + Set availableStreams = Notificator.getStreamNames(); + + final List> streamsAsData = new ArrayList>(); + Module restconfModule = this.getRestconfModule(); + final DataSchemaNode streamSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE); + for (final String streamName : availableStreams) { + streamsAsData.add(this.toStreamCompositeNode(streamName, streamSchemaNode)); + } + + final DataSchemaNode streamsSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); + QName qName = streamsSchemaNode.getQName(); + final CompositeNode streamsNode = NodeFactory.createImmutableCompositeNode(qName, null, streamsAsData); + return new StructuredData(streamsNode, streamsSchemaNode, null); + } + + @Override + public StructuredData getModules(final String identifier) { + Set modules = null; + MountInstance mountPoint = null; + if (identifier.contains(ControllerContext.MOUNT)) { + InstanceIdWithSchemaNode mountPointIdentifier = + this.controllerContext.toMountPointIdentifier(identifier); + mountPoint = mountPointIdentifier.getMountPoint(); + modules = this.controllerContext.getAllModules(mountPoint); + } + else { + throw new ResponseException(Status.BAD_REQUEST, + "URI has bad format. If modules behind mount point should be showed, URI has to end with " + + ControllerContext.MOUNT); + } + + final List> modulesAsData = new ArrayList>(); + Module restconfModule = this.getRestconfModule(); + final DataSchemaNode moduleSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + + for (final Module module : modules) { + modulesAsData.add(this.toModuleCompositeNode(module, moduleSchemaNode)); + } + + final DataSchemaNode modulesSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + QName qName = modulesSchemaNode.getQName(); + final CompositeNode modulesNode = NodeFactory.createImmutableCompositeNode(qName, null, modulesAsData); + return new StructuredData(modulesNode, modulesSchemaNode, mountPoint); + } + + @Override + public StructuredData getModule(final String identifier) { + final QName moduleNameAndRevision = this.getModuleNameAndRevision(identifier); + Module module = null; + MountInstance mountPoint = null; + if (identifier.contains(ControllerContext.MOUNT)) { + InstanceIdWithSchemaNode mountPointIdentifier = + this.controllerContext.toMountPointIdentifier(identifier); + mountPoint = mountPointIdentifier.getMountPoint(); + module = this.controllerContext.findModuleByNameAndRevision(mountPoint, moduleNameAndRevision); + } + else { + module = this.controllerContext.findModuleByNameAndRevision(moduleNameAndRevision); + } + + if (module == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Module with name '" + moduleNameAndRevision.getLocalName() + "' and revision '" + + moduleNameAndRevision.getRevision() + "' was not found."); + } + + Module restconfModule = this.getRestconfModule(); + final DataSchemaNode moduleSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + final CompositeNode moduleNode = this.toModuleCompositeNode(module, moduleSchemaNode); + return new StructuredData(moduleNode, moduleSchemaNode, mountPoint); + } + + @Override + public StructuredData getOperations() { + Set allModules = this.controllerContext.getAllModules(); + return this.operationsFromModulesToStructuredData(allModules, null); + } + + @Override + public StructuredData getOperations(final String identifier) { + Set modules = null; + MountInstance mountPoint = null; + if (identifier.contains(ControllerContext.MOUNT)) { + InstanceIdWithSchemaNode mountPointIdentifier = + this.controllerContext.toMountPointIdentifier(identifier); + mountPoint = mountPointIdentifier.getMountPoint(); + modules = this.controllerContext.getAllModules(mountPoint); + } + else { + throw new ResponseException(Status.BAD_REQUEST, + "URI has bad format. If operations behind mount point should be showed, URI has to end with " + + ControllerContext.MOUNT); + } + + return this.operationsFromModulesToStructuredData(modules, mountPoint); + } + + private StructuredData operationsFromModulesToStructuredData(final Set modules, + final MountInstance mountPoint) { + final List> operationsAsData = new ArrayList>(); + Module restconfModule = this.getRestconfModule(); + final DataSchemaNode operationsSchemaNode = + this.getSchemaNode(restconfModule, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE); + QName qName = operationsSchemaNode.getQName(); + SchemaPath path = operationsSchemaNode.getPath(); + ContainerSchemaNodeBuilder containerSchemaNodeBuilder = + new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, qName, path); + final ContainerSchemaNodeBuilder fakeOperationsSchemaNode = containerSchemaNodeBuilder; + for (final Module module : modules) { + Set rpcs = module.getRpcs(); + for (final RpcDefinition rpc : rpcs) { + QName rpcQName = rpc.getQName(); + SimpleNode immutableSimpleNode = + NodeFactory.createImmutableSimpleNode(rpcQName, null, null); + operationsAsData.add(immutableSimpleNode); + + String name = module.getName(); + LeafSchemaNodeBuilder leafSchemaNodeBuilder = new LeafSchemaNodeBuilder(name, 0, rpcQName, null); + final LeafSchemaNodeBuilder fakeRpcSchemaNode = leafSchemaNodeBuilder; + fakeRpcSchemaNode.setAugmenting(true); + + EmptyType instance = EmptyType.getInstance(); + fakeRpcSchemaNode.setType(instance); + fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build()); + } + } + + final CompositeNode operationsNode = + NodeFactory.createImmutableCompositeNode(qName, null, operationsAsData); + ContainerSchemaNode schemaNode = fakeOperationsSchemaNode.build(); + return new StructuredData(operationsNode, schemaNode, mountPoint); + } + + private Module getRestconfModule() { + QName qName = QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION, + RESTCONF_MODULE_DRAFT02_NAME); + final Module restconfModule = this.controllerContext.findModuleByNameAndRevision(qName); + if (restconfModule == null) { + throw new ResponseException(Status.INTERNAL_SERVER_ERROR, "Restconf module was not found."); + } + + return restconfModule; + } + + private QName getModuleNameAndRevision(final String identifier) { + final int mountIndex = identifier.indexOf(ControllerContext.MOUNT); + String moduleNameAndRevision = ""; + if (mountIndex >= 0) { + moduleNameAndRevision = identifier.substring(mountIndex + ControllerContext.MOUNT.length()); + } + else { + moduleNameAndRevision = identifier; + } + + Splitter splitter = Splitter.on("/").omitEmptyStrings(); + Iterable split = splitter.split(moduleNameAndRevision); + final List pathArgs = Lists.newArrayList(split); + if (pathArgs.size() < 2) { + throw new ResponseException(Status.BAD_REQUEST, + "URI has bad format. End of URI should be in format \'moduleName/yyyy-MM-dd\'"); + } + + try { + final String moduleName = pathArgs.get( 0 ); + String revision = pathArgs.get(1); + final Date moduleRevision = REVISION_FORMAT.parse(revision); + return QName.create(null, moduleRevision, moduleName); + } + catch (ParseException e) { + throw new ResponseException(Status.BAD_REQUEST, "URI has bad format. It should be \'moduleName/yyyy-MM-dd\'"); + } + } + + private CompositeNode toStreamCompositeNode(final String streamName, final DataSchemaNode streamSchemaNode) { + final List> streamNodeValues = new ArrayList>(); + List instanceDataChildrenByName = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) streamSchemaNode), + "name"); + final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + streamNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.getQName(), null, + streamName)); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) streamSchemaNode), "description"); + final DataSchemaNode descriptionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + streamNodeValues.add(NodeFactory.createImmutableSimpleNode(descriptionSchemaNode.getQName(), null, + "DESCRIPTION_PLACEHOLDER")); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) streamSchemaNode), "replay-support"); + final DataSchemaNode replaySupportSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replaySupportSchemaNode.getQName(), null, + Boolean.valueOf(true))); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) streamSchemaNode), "replay-log-creation-time"); + final DataSchemaNode replayLogCreationTimeSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replayLogCreationTimeSchemaNode.getQName(), + null, "")); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) streamSchemaNode), "events"); + final DataSchemaNode eventsSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + streamNodeValues.add(NodeFactory.createImmutableSimpleNode(eventsSchemaNode.getQName(), + null, "")); + + return NodeFactory.createImmutableCompositeNode(streamSchemaNode.getQName(), null, streamNodeValues); + } + + private CompositeNode toModuleCompositeNode(final Module module, final DataSchemaNode moduleSchemaNode) { + final List> moduleNodeValues = new ArrayList>(); + List instanceDataChildrenByName = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) moduleSchemaNode), "name"); + final DataSchemaNode nameSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.getQName(), + null, module.getName())); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) moduleSchemaNode), "revision"); + final DataSchemaNode revisionSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + Date _revision = module.getRevision(); + moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(revisionSchemaNode.getQName(), null, + REVISION_FORMAT.format(_revision))); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) moduleSchemaNode), "namespace"); + final DataSchemaNode namespaceSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(namespaceSchemaNode.getQName(), null, + module.getNamespace().toString())); + + instanceDataChildrenByName = this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) moduleSchemaNode), "feature"); + final DataSchemaNode featureSchemaNode = Iterables.getFirst(instanceDataChildrenByName, null); + for (final FeatureDefinition feature : module.getFeatures()) { + moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(featureSchemaNode.getQName(), null, + feature.getQName().getLocalName())); + } + + return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.getQName(), null, moduleNodeValues); + } + + private DataSchemaNode getSchemaNode(final Module restconfModule, final String schemaNodeName) { + Set groupings = restconfModule.getGroupings(); + + final Predicate filter = new Predicate() { + @Override + public boolean apply(final GroupingDefinition g) { + return Objects.equal(g.getQName().getLocalName(), + RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE); + } + }; + + Iterable filteredGroups = Iterables.filter(groupings, filter); + + final GroupingDefinition restconfGrouping = Iterables.getFirst(filteredGroups, null); + + List instanceDataChildrenByName = + this.controllerContext.findInstanceDataChildrenByName(restconfGrouping, + RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE); + final DataSchemaNode restconfContainer = Iterables.getFirst(instanceDataChildrenByName, null); + + if (Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE)) { + List instances = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), + RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE); + return Iterables.getFirst(instances, null); + } + else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE)) { + List instances = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), + RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); + return Iterables.getFirst(instances, null); + } + else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE)) { + List instances = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), + RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE); + final DataSchemaNode modules = Iterables.getFirst(instances, null); + instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules), + RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE); + return Iterables.getFirst(instances, null); + } + else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE)) { + List instances = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), + RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + return Iterables.getFirst(instances, null); + } + else if(Objects.equal(schemaNodeName, RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE)) { + List instances = + this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) restconfContainer), + RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE); + final DataSchemaNode modules = Iterables.getFirst(instances, null); + instances = this.controllerContext.findInstanceDataChildrenByName(((DataNodeContainer) modules), + RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE); + return Iterables.getFirst(instances, null); + } + + return null; + } + + @Override + public Object getRoot() { + return null; + } + + @Override + public StructuredData invokeRpc(final String identifier, final CompositeNode payload) { + final RpcDefinition rpc = this.resolveIdentifierInInvokeRpc(identifier); + if (Objects.equal(rpc.getQName().getNamespace().toString(), SAL_REMOTE_NAMESPACE) && + Objects.equal(rpc.getQName().getLocalName(), SAL_REMOTE_RPC_SUBSRCIBE)) { + + final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); + final SimpleNode pathNode = value == null ? null : + value.getFirstSimpleByName( QName.create(rpc.getQName(), "path") ); + final Object pathValue = pathNode == null ? null : pathNode.getValue(); + + if (!(pathValue instanceof InstanceIdentifier)) { + throw new ResponseException(Status.INTERNAL_SERVER_ERROR, + "Instance identifier was not normalized correctly."); + } + + final InstanceIdentifier pathIdentifier = ((InstanceIdentifier) pathValue); + String streamName = null; + if (!Iterables.isEmpty(pathIdentifier.getPath())) { + String fullRestconfIdentifier = this.controllerContext.toFullRestconfIdentifier(pathIdentifier); + streamName = Notificator.createStreamNameFromUri(fullRestconfIdentifier); + } + + if (Strings.isNullOrEmpty(streamName)) { + throw new ResponseException(Status.BAD_REQUEST, + "Path is empty or contains data node which is not Container or List build-in type."); + } + + final SimpleNode streamNameNode = NodeFactory.createImmutableSimpleNode( + QName.create(rpc.getOutput().getQName(), "stream-name"), null, streamName); + final List> output = new ArrayList>(); + output.add(streamNameNode); + + final MutableCompositeNode responseData = NodeFactory.createMutableCompositeNode( + rpc.getOutput().getQName(), null, output, null, null); + + if (!Notificator.existListenerFor(pathIdentifier)) { + Notificator.createListener(pathIdentifier, streamName); + } + + return new StructuredData(responseData, rpc.getOutput(), null); + } + + RpcDefinition rpcDefinition = this.controllerContext.getRpcDefinition(identifier); + return this.callRpc(rpcDefinition, payload); + } + + @Override + public StructuredData invokeRpc(final String identifier, final String noPayload) { + if (!Strings.isNullOrEmpty(noPayload)) { + throw new ResponseException(Status.UNSUPPORTED_MEDIA_TYPE, + "Content-Type contains unsupported Media Type."); + } + + final RpcDefinition rpc = this.resolveIdentifierInInvokeRpc(identifier); + return this.callRpc(rpc, null); + } + + private RpcDefinition resolveIdentifierInInvokeRpc(final String identifier) { + if (identifier.indexOf("/") < 0) { + final String identifierDecoded = this.controllerContext.urlPathArgDecode(identifier); + final RpcDefinition rpc = this.controllerContext.getRpcDefinition(identifierDecoded); + if (rpc != null) { + return rpc; + } + + throw new ResponseException(Status.NOT_FOUND, "RPC does not exist."); + } + + final String slashErrorMsg = String.format( + "Identifier %n%s%ncan\'t contain slash character (/).%nIf slash is part of identifier name then use %%2F placeholder.", + identifier); + + throw new ResponseException(Status.NOT_FOUND, slashErrorMsg); + } + + private StructuredData callRpc(final RpcDefinition rpc, final CompositeNode payload) { + if (rpc == null) { + throw new ResponseException(Status.NOT_FOUND, "RPC does not exist."); + } + + CompositeNode rpcRequest = null; + if (payload == null) { + rpcRequest = NodeFactory.createMutableCompositeNode(rpc.getQName(), null, null, null, null); + } + else { + final CompositeNode value = this.normalizeNode(payload, rpc.getInput(), null); + final List> input = new ArrayList>(); + input.add(value); + + rpcRequest = NodeFactory.createMutableCompositeNode(rpc.getQName(), null, input, null, null); + } + + final RpcResult rpcResult = broker.invokeRpc(rpc.getQName(), rpcRequest); + + if (!rpcResult.isSuccessful()) { + throw new ResponseException(Status.INTERNAL_SERVER_ERROR, "Operation failed"); + } + + CompositeNode result = rpcResult.getResult(); + if (result == null) { + return null; + } + + return new StructuredData(result, rpc.getOutput(), null); + } + + @Override + public StructuredData readConfigurationData(final String identifier) { + final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + CompositeNode data = null; + MountInstance mountPoint = iiWithData.getMountPoint(); + if (mountPoint != null) { + data = broker.readConfigurationDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier()); + } + else { + data = broker.readConfigurationData(iiWithData.getInstanceIdentifier()); + } + + return new StructuredData(data, iiWithData.getSchemaNode(), iiWithData.getMountPoint()); + } + + @Override + public StructuredData readOperationalData(final String identifier) { + final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + CompositeNode data = null; + MountInstance mountPoint = iiWithData.getMountPoint(); + if (mountPoint != null) { + data = broker.readOperationalDataBehindMountPoint(mountPoint, iiWithData.getInstanceIdentifier()); + } + else { + data = broker.readOperationalData(iiWithData.getInstanceIdentifier()); + } + + return new StructuredData(data, iiWithData.getSchemaNode(), mountPoint); + } + + @Override + public Response updateConfigurationData(final String identifier, final CompositeNode payload) { + final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + MountInstance mountPoint = iiWithData.getMountPoint(); + final CompositeNode value = this.normalizeNode(payload, iiWithData.getSchemaNode(), mountPoint); + RpcResult status = null; + + try { + if (mountPoint != null) { + status = broker.commitConfigurationDataPutBehindMountPoint( + mountPoint, iiWithData.getInstanceIdentifier(), value).get(); + } else { + status = broker.commitConfigurationDataPut(iiWithData.getInstanceIdentifier(), value).get(); + } + } + catch( Exception e ) { + throw new ResponseException( e, "Error updating data" ); + } + + if( status.getResult() == TransactionStatus.COMMITED ) + return Response.status(Status.OK).build(); + + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + @Override + public Response createConfigurationData(final String identifier, final CompositeNode payload) { + URI payloadNS = this.namespace(payload); + if (payloadNS == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); + } + + InstanceIdWithSchemaNode iiWithData = null; + CompositeNode value = null; + if (this.representsMountPointRootData(payload)) { + // payload represents mount point data and URI represents path to the mount point + + if (this.endsWithMountPoint(identifier)) { + throw new ResponseException(Status.BAD_REQUEST, + "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + + "\" for POST operation."); + } + + final String completeIdentifier = this.addMountPointIdentifier(identifier); + iiWithData = this.controllerContext.toInstanceIdentifier(completeIdentifier); + + value = this.normalizeNode(payload, iiWithData.getSchemaNode(), iiWithData.getMountPoint()); + } + else { + final InstanceIdWithSchemaNode incompleteInstIdWithData = + this.controllerContext.toInstanceIdentifier(identifier); + final DataNodeContainer parentSchema = (DataNodeContainer) incompleteInstIdWithData.getSchemaNode(); + MountInstance mountPoint = incompleteInstIdWithData.getMountPoint(); + final Module module = this.findModule(mountPoint, payload); + if (module == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Module was not found for \"" + payloadNS + "\""); + } + + String payloadName = this.getName(payload); + final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace( + parentSchema, payloadName, module.getNamespace()); + value = this.normalizeNode(payload, schemaNode, mountPoint); + + iiWithData = this.addLastIdentifierFromData(incompleteInstIdWithData, value, schemaNode); + } + + RpcResult status = null; + MountInstance mountPoint = iiWithData.getMountPoint(); + try { + if (mountPoint != null) { + Future> future = + broker.commitConfigurationDataPostBehindMountPoint( + mountPoint, iiWithData.getInstanceIdentifier(), value); + status = future == null ? null : future.get(); + } + else { + Future> future = + broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value); + status = future == null ? null : future.get(); + } + } + catch( Exception e ) { + throw new ResponseException( e, "Error creating data" ); + } + + if (status == null) { + return Response.status(Status.ACCEPTED).build(); + } + + if( status.getResult() == TransactionStatus.COMMITED ) + return Response.status(Status.NO_CONTENT).build(); + + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + @Override + public Response createConfigurationData(final CompositeNode payload) { + URI payloadNS = this.namespace(payload); + if (payloadNS == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); + } + + final Module module = this.findModule(null, payload); + if (module == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)"); + } + + String payloadName = this.getName(payload); + final DataSchemaNode schemaNode = this.controllerContext.findInstanceDataChildByNameAndNamespace( + module, payloadName, module.getNamespace()); + final CompositeNode value = this.normalizeNode(payload, schemaNode, null); + final InstanceIdWithSchemaNode iiWithData = this.addLastIdentifierFromData(null, value, schemaNode); + RpcResult status = null; + MountInstance mountPoint = iiWithData.getMountPoint(); + + try { + if (mountPoint != null) { + Future> future = + broker.commitConfigurationDataPostBehindMountPoint( + mountPoint, iiWithData.getInstanceIdentifier(), value); + status = future == null ? null : future.get(); + } + else { + Future> future = + broker.commitConfigurationDataPost(iiWithData.getInstanceIdentifier(), value); + status = future == null ? null : future.get(); + } + } + catch( Exception e ) { + throw new ResponseException( e, "Error creating data" ); + } + + if (status == null) { + return Response.status(Status.ACCEPTED).build(); + } + + if( status.getResult() == TransactionStatus.COMMITED ) + return Response.status(Status.NO_CONTENT).build(); + + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + @Override + public Response deleteConfigurationData(final String identifier) { + final InstanceIdWithSchemaNode iiWithData = this.controllerContext.toInstanceIdentifier(identifier); + RpcResult status = null; + MountInstance mountPoint = iiWithData.getMountPoint(); + + try { + if (mountPoint != null) { + status = broker.commitConfigurationDataDeleteBehindMountPoint( + mountPoint, iiWithData.getInstanceIdentifier()).get(); + } + else { + status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier()).get(); + } + } + catch( Exception e ) { + throw new ResponseException( e, "Error creating data" ); + } + + if( status.getResult() == TransactionStatus.COMMITED ) + return Response.status(Status.OK).build(); + + return Response.status(Status.INTERNAL_SERVER_ERROR).build(); + } + + @Override + public Response subscribeToStream(final String identifier, final UriInfo uriInfo) { + final String streamName = Notificator.createStreamNameFromUri(identifier); + if (Strings.isNullOrEmpty(streamName)) { + throw new ResponseException(Status.BAD_REQUEST, "Stream name is empty."); + } + + final ListenerAdapter listener = Notificator.getListenerFor(streamName); + if (listener == null) { + throw new ResponseException(Status.BAD_REQUEST, "Stream was not found."); + } + + broker.registerToListenDataChanges(listener); + + final UriBuilder uriBuilder = uriInfo.getAbsolutePathBuilder(); + UriBuilder port = uriBuilder.port(WebSocketServer.PORT); + final URI uriToWebsocketServer = port.replacePath(streamName).build(); + + return Response.status(Status.OK).location(uriToWebsocketServer).build(); + } + + private Module findModule(final MountInstance mountPoint, final CompositeNode data) { + if (data instanceof CompositeNodeWrapper) { + return findModule(mountPoint, (CompositeNodeWrapper)data); + } + else if (data != null) { + URI namespace = data.getNodeType().getNamespace(); + if (mountPoint != null) { + return this.controllerContext.findModuleByNamespace(mountPoint, namespace); + } + else { + return this.controllerContext.findModuleByNamespace(namespace); + } + } + else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(mountPoint, data).toString()); + } + } + + private Module findModule(final MountInstance mountPoint, final CompositeNodeWrapper data) { + URI namespace = data.getNamespace(); + Preconditions.checkNotNull(namespace); + + Module module = null; + if (mountPoint != null) { + module = this.controllerContext.findModuleByNamespace(mountPoint, namespace); + if (module == null) { + module = this.controllerContext.findModuleByName(mountPoint, namespace.toString()); + } + } + else { + module = this.controllerContext.findModuleByNamespace(namespace); + if (module == null) { + module = this.controllerContext.findModuleByName(namespace.toString()); + } + } + + return module; + } + + private InstanceIdWithSchemaNode addLastIdentifierFromData( + final InstanceIdWithSchemaNode identifierWithSchemaNode, + final CompositeNode data, final DataSchemaNode schemaOfData) { + InstanceIdentifier instanceIdentifier = null; + if (identifierWithSchemaNode != null) { + instanceIdentifier = identifierWithSchemaNode.getInstanceIdentifier(); + } + + final InstanceIdentifier iiOriginal = instanceIdentifier; + InstanceIdentifierBuilder iiBuilder = null; + if (iiOriginal == null) { + iiBuilder = InstanceIdentifier.builder(); + } + else { + iiBuilder = InstanceIdentifier.builder(iiOriginal); + } + + if ((schemaOfData instanceof ListSchemaNode)) { + HashMap keys = this.resolveKeysFromData(((ListSchemaNode) schemaOfData), data); + iiBuilder.nodeWithKey(schemaOfData.getQName(), keys); + } + else { + iiBuilder.node(schemaOfData.getQName()); + } + + InstanceIdentifier instance = iiBuilder.toInstance(); + MountInstance mountPoint = null; + if (identifierWithSchemaNode != null) { + mountPoint=identifierWithSchemaNode.getMountPoint(); + } + + return new InstanceIdWithSchemaNode(instance, schemaOfData, mountPoint); + } + + private HashMap resolveKeysFromData(final ListSchemaNode listNode, + final CompositeNode dataNode) { + final HashMap keyValues = new HashMap(); + List _keyDefinition = listNode.getKeyDefinition(); + for (final QName key : _keyDefinition) { + SimpleNode head = null; + String localName = key.getLocalName(); + List> simpleNodesByName = dataNode.getSimpleNodesByName(localName); + if (simpleNodesByName != null) { + head = Iterables.getFirst(simpleNodesByName, null); + } + + Object dataNodeKeyValueObject = null; + if (head != null) { + dataNodeKeyValueObject = head.getValue(); + } + + if (dataNodeKeyValueObject == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data contains list \"" + dataNode.getNodeType().getLocalName() + + "\" which does not contain key: \"" + key.getLocalName() + "\""); + } + + keyValues.put(key, dataNodeKeyValueObject); + } + + return keyValues; + } + + private boolean endsWithMountPoint(final String identifier) { + return identifier.endsWith(ControllerContext.MOUNT) || + identifier.endsWith(ControllerContext.MOUNT + "/"); + } + + private boolean representsMountPointRootData(final CompositeNode data) { + URI namespace = this.namespace(data); + return (SchemaContext.NAME.getNamespace().equals( namespace ) /* || + MOUNT_POINT_MODULE_NAME.equals( namespace.toString() )*/ ) && + SchemaContext.NAME.getLocalName().equals( this.localName(data) ); + } + + private String addMountPointIdentifier(final String identifier) { + boolean endsWith = identifier.endsWith("/"); + if (endsWith) { + return (identifier + ControllerContext.MOUNT); + } + + return identifier + "/" + ControllerContext.MOUNT; + } + + private CompositeNode normalizeNode(final CompositeNode node, final DataSchemaNode schema, + final MountInstance mountPoint) { + if (schema == null) { + QName nodeType = node == null ? null : node.getNodeType(); + String localName = nodeType == null ? null : nodeType.getLocalName(); + String _plus = ("Data schema node was not found for " + localName); + throw new ResponseException(Status.INTERNAL_SERVER_ERROR, + "Data schema node was not found for " + localName ); + } + + if (!(schema instanceof DataNodeContainer)) { + throw new ResponseException(Status.BAD_REQUEST, + "Root element has to be container or list yang datatype."); + } + + if ((node instanceof CompositeNodeWrapper)) { + boolean isChangeAllowed = ((CompositeNodeWrapper) node).isChangeAllowed(); + if (isChangeAllowed) { + try { + this.normalizeNode(((CompositeNodeWrapper) node), schema, null, mountPoint); + } + catch (NumberFormatException e) { + throw new ResponseException(Status.BAD_REQUEST, e.getMessage()); + } + } + + return ((CompositeNodeWrapper) node).unwrap(); + } + + return node; + } + + private void normalizeNode(final NodeWrapper nodeBuilder, + final DataSchemaNode schema, final QName previousAugment, + final MountInstance mountPoint) { + if (schema == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data has bad format.\n\"" + nodeBuilder.getLocalName() + + "\" does not exist in yang schema."); + } + + QName currentAugment = null; + if (nodeBuilder.getQname() != null) { + currentAugment = previousAugment; + } + else { + currentAugment = this.normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint); + if (nodeBuilder.getQname() == null) { + throw new ResponseException(Status.BAD_REQUEST, + "Data has bad format.\nIf data is in XML format then namespace for \"" + + nodeBuilder.getLocalName() + + "\" should be \"" + schema.getQName().getNamespace() + "\".\n" + + "If data is in JSON format then module name for \"" + nodeBuilder.getLocalName() + + "\" should be corresponding to namespace \"" + + schema.getQName().getNamespace() + "\"."); + } + } + + if ((nodeBuilder instanceof CompositeNodeWrapper)) { + final List> children = ((CompositeNodeWrapper) nodeBuilder).getValues(); + for (final NodeWrapper child : children) { + final List potentialSchemaNodes = + this.controllerContext.findInstanceDataChildrenByName( + ((DataNodeContainer) schema), child.getLocalName()); + + if (potentialSchemaNodes.size() > 1 && child.getNamespace() == null) { + StringBuilder builder = new StringBuilder(); + for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) { + builder.append(" ").append(potentialSchemaNode.getQName().getNamespace().toString()) + .append("\n"); + } + + throw new ResponseException(Status.BAD_REQUEST, + "Node \"" + child.getLocalName() + + "\" is added as augment from more than one module. " + + "Therefore node must have namespace (XML format) or module name (JSON format)." + + "\nThe node is added as augment from modules with namespaces:\n" + builder); + } + + boolean rightNodeSchemaFound = false; + for (final DataSchemaNode potentialSchemaNode : potentialSchemaNodes) { + if (!rightNodeSchemaFound) { + final QName potentialCurrentAugment = + this.normalizeNodeName(child, potentialSchemaNode, currentAugment, mountPoint); + if (child.getQname() != null ) { + this.normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint); + rightNodeSchemaFound = true; + } + } + } + + if (!rightNodeSchemaFound) { + throw new ResponseException(Status.BAD_REQUEST, + "Schema node \"" + child.getLocalName() + "\" was not found in module."); + } + } + + if ((schema instanceof ListSchemaNode)) { + final List listKeys = ((ListSchemaNode) schema).getKeyDefinition(); + for (final QName listKey : listKeys) { + boolean foundKey = false; + for (final NodeWrapper child : children) { + if (Objects.equal(child.unwrap().getNodeType().getLocalName(), listKey.getLocalName())) { + foundKey = true; + } + } + + if (!foundKey) { + throw new ResponseException(Status.BAD_REQUEST, + "Missing key in URI \"" + listKey.getLocalName() + + "\" of list \"" + schema.getQName().getLocalName() + "\""); + } + } + } + } + else { + if ((nodeBuilder instanceof SimpleNodeWrapper)) { + final SimpleNodeWrapper simpleNode = ((SimpleNodeWrapper) nodeBuilder); + final Object value = simpleNode.getValue(); + Object inputValue = value; + TypeDefinition typeDefinition = this.typeDefinition(schema); + if ((typeDefinition instanceof IdentityrefTypeDefinition)) { + if ((value instanceof String)) { + inputValue = new IdentityValuesDTO( nodeBuilder.getNamespace().toString(), + (String) value, null, (String) value ); + } // else value is already instance of IdentityValuesDTO + } + + Codec codec = RestCodec.from(typeDefinition, mountPoint); + Object outputValue = codec == null ? null : codec.deserialize(inputValue); + + simpleNode.setValue(outputValue); + } + else { + if ((nodeBuilder instanceof EmptyNodeWrapper)) { + final EmptyNodeWrapper emptyNodeBuilder = ((EmptyNodeWrapper) nodeBuilder); + if ((schema instanceof LeafSchemaNode)) { + emptyNodeBuilder.setComposite(false); + } + else { + if ((schema instanceof ContainerSchemaNode)) { + // FIXME: Add presence check + emptyNodeBuilder.setComposite(true); + } + } + } + } + } + } + + private QName normalizeNodeName(final NodeWrapper nodeBuilder, + final DataSchemaNode schema, final QName previousAugment, + final MountInstance mountPoint) { + QName validQName = schema.getQName(); + QName currentAugment = previousAugment; + if (schema.isAugmenting()) { + currentAugment = schema.getQName(); + } + else if (previousAugment != null && + !Objects.equal( schema.getQName().getNamespace(), previousAugment.getNamespace())) { + validQName = QName.create(currentAugment, schema.getQName().getLocalName()); + } + + String moduleName = null; + if (mountPoint == null) { + moduleName = controllerContext.findModuleNameByNamespace(validQName.getNamespace()); + } + else { + moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.getNamespace()); + } + + if (nodeBuilder.getNamespace() == null || + Objects.equal(nodeBuilder.getNamespace(), validQName.getNamespace()) || + Objects.equal(nodeBuilder.getNamespace().toString(), moduleName) /*|| + Note: this check is wrong - can never be true as it compares a URI with a String + not sure what the intention is so commented out... + Objects.equal(nodeBuilder.getNamespace(), MOUNT_POINT_MODULE_NAME)*/ ) { + + nodeBuilder.setQname(validQName); + } + + return currentAugment; + } + + private URI namespace(final CompositeNode data) { + if (data instanceof CompositeNodeWrapper) { + return ((CompositeNodeWrapper)data).getNamespace(); + } + else if (data != null) { + return data.getNodeType().getNamespace(); + } + else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(data).toString()); + } + } + + private String localName(final CompositeNode data) { + if (data instanceof CompositeNodeWrapper) { + return ((CompositeNodeWrapper)data).getLocalName(); + } + else if (data != null) { + return data.getNodeType().getLocalName(); + } + else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(data).toString()); + } + } + + private String getName(final CompositeNode data) { + if (data instanceof CompositeNodeWrapper) { + return ((CompositeNodeWrapper)data).getLocalName(); + } + else if (data != null) { + return data.getNodeType().getLocalName(); + } + else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(data).toString()); + } + } + + private TypeDefinition _typeDefinition(final LeafSchemaNode node) { + TypeDefinition baseType = node.getType(); + while (baseType.getBaseType() != null) { + baseType = baseType.getBaseType(); + } + + return baseType; + } + + private TypeDefinition typeDefinition(final LeafListSchemaNode node) { + TypeDefinition baseType = node.getType(); + while (baseType.getBaseType() != null) { + baseType = baseType.getBaseType(); + } + + return baseType; + } + + private TypeDefinition typeDefinition(final DataSchemaNode node) { + if (node instanceof LeafListSchemaNode) { + return typeDefinition((LeafListSchemaNode)node); + } + else if (node instanceof LeafSchemaNode) { + return _typeDefinition((LeafSchemaNode)node); + } + else { + throw new IllegalArgumentException("Unhandled parameter types: " + + Arrays.asList(node).toString()); + } + } +} diff --git a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend b/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend deleted file mode 100644 index f1901d7112..0000000000 --- a/opendaylight/md-sal/sal-rest-connector/src/main/java/org/opendaylight/controller/sal/restconf/impl/RestconfImpl.xtend +++ /dev/null @@ -1,732 +0,0 @@ -/* - * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v1.0 which accompanies this distribution, - * and is available at http://www.eclipse.org/legal/epl-v10.html - */ -package org.opendaylight.controller.sal.restconf.impl - -import com.google.common.base.Preconditions -import com.google.common.base.Splitter -import com.google.common.collect.Lists -import java.net.URI -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.ArrayList -import java.util.HashMap -import java.util.List -import java.util.Set -import javax.ws.rs.core.Response -import javax.ws.rs.core.UriInfo -import org.opendaylight.controller.md.sal.common.api.TransactionStatus -import org.opendaylight.controller.sal.core.api.mount.MountInstance -import org.opendaylight.controller.sal.rest.api.RestconfService -import org.opendaylight.controller.sal.streams.listeners.Notificator -import org.opendaylight.controller.sal.streams.websockets.WebSocketServer -import org.opendaylight.yangtools.yang.common.QName -import org.opendaylight.yangtools.yang.common.RpcResult -import org.opendaylight.yangtools.yang.data.api.CompositeNode -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier -import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier.InstanceIdentifierBuilder -import org.opendaylight.yangtools.yang.data.api.Node -import org.opendaylight.yangtools.yang.data.impl.NodeFactory -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode -import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode -import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode -import org.opendaylight.yangtools.yang.model.api.Module -import org.opendaylight.yangtools.yang.model.api.RpcDefinition -import org.opendaylight.yangtools.yang.model.api.SchemaContext -import org.opendaylight.yangtools.yang.model.api.TypeDefinition -import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition -import org.opendaylight.yangtools.yang.model.util.EmptyType -import org.opendaylight.yangtools.yang.parser.builder.impl.ContainerSchemaNodeBuilder -import org.opendaylight.yangtools.yang.parser.builder.impl.LeafSchemaNodeBuilder - -import static javax.ws.rs.core.Response.Status.* - -class RestconfImpl implements RestconfService { - - val static RestconfImpl INSTANCE = new RestconfImpl - val static MOUNT_POINT_MODULE_NAME = "ietf-netconf" - val static REVISION_FORMAT = new SimpleDateFormat("yyyy-MM-dd") - val static RESTCONF_MODULE_DRAFT02_REVISION = "2013-10-19" - val static RESTCONF_MODULE_DRAFT02_NAME = "ietf-restconf" - val static RESTCONF_MODULE_DRAFT02_NAMESPACE = "urn:ietf:params:xml:ns:yang:ietf-restconf" - val static RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE = "restconf" - val static RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE = "restconf" - val static RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE = "modules" - val static RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE = "module" - val static RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE = "streams" - val static RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE = "stream" - val static RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE = "operations" - val static SAL_REMOTE_NAMESPACE = "urn:opendaylight:params:xml:ns:yang:controller:md:sal:remote" - val static SAL_REMOTE_RPC_SUBSRCIBE = "create-data-change-event-subscription" - - @Property - BrokerFacade broker - - @Property - extension ControllerContext controllerContext - - private new() { - if (INSTANCE !== null) { - throw new IllegalStateException("Already instantiated"); - } - } - - static def getInstance() { - return INSTANCE - } - - override getModules() { - val restconfModule = getRestconfModule() - val List> modulesAsData = new ArrayList - val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) - for (module : allModules) { - modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode)) - } - val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE) - val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData) - return new StructuredData(modulesNode, modulesSchemaNode, null) - } - - override getAvailableStreams(){ - var Set availableStreams = Notificator.getStreamNames(); - val List> streamsAsData = new ArrayList - val streamSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE) - for (String streamName:availableStreams){ - streamsAsData.add(streamName.toStreamCompositeNode(streamSchemaNode)) - } - val streamsSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE) - val streamsNode = NodeFactory.createImmutableCompositeNode(streamsSchemaNode.QName, null, streamsAsData) - return new StructuredData(streamsNode, streamsSchemaNode, null) - } - override getModules(String identifier) { - var Set modules = null - var MountInstance mountPoint = null - if (identifier.contains(ControllerContext.MOUNT)) { - mountPoint = identifier.toMountPointIdentifier.mountPoint - modules = mountPoint.allModules - } else { - throw new ResponseException(BAD_REQUEST, "URI has bad format. If modules behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT) - } - val List> modulesAsData = new ArrayList - val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) - for (module : modules) { - modulesAsData.add(module.toModuleCompositeNode(moduleSchemaNode)) - } - val modulesSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE) - val modulesNode = NodeFactory.createImmutableCompositeNode(modulesSchemaNode.QName, null, modulesAsData) - return new StructuredData(modulesNode, modulesSchemaNode, mountPoint) - } - - override getModule(String identifier) { - val moduleNameAndRevision = identifier.moduleNameAndRevision - var Module module = null - var MountInstance mountPoint = null - if (identifier.contains(ControllerContext.MOUNT)) { - mountPoint = identifier.toMountPointIdentifier.mountPoint - module = mountPoint.findModuleByNameAndRevision(moduleNameAndRevision) - } else { - module = findModuleByNameAndRevision(moduleNameAndRevision) - } - if (module === null) { - throw new ResponseException(BAD_REQUEST, - "Module with name '" + moduleNameAndRevision.localName + "' and revision '" + - moduleNameAndRevision.revision + "' was not found.") - } - val moduleSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) - val moduleNode = module.toModuleCompositeNode(moduleSchemaNode) - return new StructuredData(moduleNode, moduleSchemaNode, mountPoint) - } - - override getOperations() { - return operationsFromModulesToStructuredData(allModules,null) - } - - override getOperations(String identifier) { - var Set modules = null - var MountInstance mountPoint = null - if (identifier.contains(ControllerContext.MOUNT)) { - mountPoint = identifier.toMountPointIdentifier.mountPoint - modules = mountPoint.allModules - } else { - throw new ResponseException(BAD_REQUEST, "URI has bad format. If operations behind mount point should be showed, URI has to end with " + ControllerContext.MOUNT) - } - return operationsFromModulesToStructuredData(modules,mountPoint) - } - - private def StructuredData operationsFromModulesToStructuredData(Set modules,MountInstance mountPoint) { - val List> operationsAsData = new ArrayList - val operationsSchemaNode = restconfModule.getSchemaNode(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE) - val fakeOperationsSchemaNode = new ContainerSchemaNodeBuilder(RESTCONF_MODULE_DRAFT02_NAME, 0, operationsSchemaNode.QName, operationsSchemaNode.path) - for (module : modules) { - for (rpc : module.rpcs) { - operationsAsData.add(NodeFactory.createImmutableSimpleNode(rpc.QName, null, null)) - val fakeRpcSchemaNode = new LeafSchemaNodeBuilder(module.name, 0, rpc.QName, null) - fakeRpcSchemaNode.setAugmenting(true) - fakeRpcSchemaNode.setType(EmptyType.instance) - fakeOperationsSchemaNode.addChildNode(fakeRpcSchemaNode.build) - } - } - val operationsNode = NodeFactory.createImmutableCompositeNode(operationsSchemaNode.QName, null, operationsAsData) - return new StructuredData(operationsNode, fakeOperationsSchemaNode.build, mountPoint) - } - - private def Module getRestconfModule() { - val restconfModule = findModuleByNameAndRevision( - QName.create(RESTCONF_MODULE_DRAFT02_NAMESPACE, RESTCONF_MODULE_DRAFT02_REVISION, - RESTCONF_MODULE_DRAFT02_NAME)) - if (restconfModule === null) { - throw new ResponseException(INTERNAL_SERVER_ERROR, "Restconf module was not found.") - } - return restconfModule - } - - private def QName getModuleNameAndRevision(String identifier) { - val indexOfMountPointFirstLetter = identifier.indexOf(ControllerContext.MOUNT) - var moduleNameAndRevision = ""; - if (indexOfMountPointFirstLetter !== -1) { // module and revision is behind mount point string - moduleNameAndRevision = identifier.substring(indexOfMountPointFirstLetter + ControllerContext.MOUNT.length) - } else ( - moduleNameAndRevision = identifier - ) - val pathArgs = Lists.newArrayList(Splitter.on("/").omitEmptyStrings.split(moduleNameAndRevision)) - if (pathArgs.length < 2) { - throw new ResponseException(BAD_REQUEST, - "URI has bad format. End of URI should be in format 'moduleName/yyyy-MM-dd'") - } - try { - val moduleName = pathArgs.head - val moduleRevision = REVISION_FORMAT.parse(pathArgs.get(1)) - return QName.create(null, moduleRevision, moduleName) - } catch(ParseException e) { - throw new ResponseException(BAD_REQUEST, "URI has bad format. It should be 'moduleName/yyyy-MM-dd'") - } - } - - private def CompositeNode toStreamCompositeNode(String streamName, DataSchemaNode streamSchemaNode) { - val List> streamNodeValues = new ArrayList - val nameSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("name").head - streamNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.QName, null, streamName)) - - val descriptionSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("description").head - streamNodeValues.add(NodeFactory.createImmutableSimpleNode(descriptionSchemaNode.QName, null, "DESCRIPTION_PLACEHOLDER")) - - val replaySupportSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("replay-support").head - streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replaySupportSchemaNode.QName, null, true)) - - val replayLogCreationTimeSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("replay-log-creation-time").head - streamNodeValues.add(NodeFactory.createImmutableSimpleNode(replayLogCreationTimeSchemaNode.QName, null, "")) - - val eventsSchemaNode = (streamSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("events").head - streamNodeValues.add(NodeFactory.createImmutableSimpleNode(eventsSchemaNode.QName, null, "")) - - return NodeFactory.createImmutableCompositeNode(streamSchemaNode.QName, null, streamNodeValues) - } - private def CompositeNode toModuleCompositeNode(Module module, DataSchemaNode moduleSchemaNode) { - val List> moduleNodeValues = new ArrayList - val nameSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("name").head - moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(nameSchemaNode.QName, null, module.name)) - val revisionSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("revision").head - moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(revisionSchemaNode.QName, null, REVISION_FORMAT.format(module.revision))) - val namespaceSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("namespace").head - moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(namespaceSchemaNode.QName, null, module.namespace.toString)) - val featureSchemaNode = (moduleSchemaNode as DataNodeContainer).findInstanceDataChildrenByName("feature").head - for (feature : module.features) { - moduleNodeValues.add(NodeFactory.createImmutableSimpleNode(featureSchemaNode.QName, null, feature.QName.localName)) - } - return NodeFactory.createImmutableCompositeNode(moduleSchemaNode.QName, null, moduleNodeValues) - } - - private def DataSchemaNode getSchemaNode(Module restconfModule, String schemaNodeName) { - val restconfGrouping = restconfModule.groupings.filter[g|g.QName.localName == RESTCONF_MODULE_DRAFT02_RESTCONF_GROUPING_SCHEMA_NODE].head - val restconfContainer = restconfGrouping.findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_RESTCONF_CONTAINER_SCHEMA_NODE).head - if (schemaNodeName == RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE) { - return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_OPERATIONS_CONTAINER_SCHEMA_NODE).head - } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE) { - return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE).head - } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE) { - val modules = (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAMS_CONTAINER_SCHEMA_NODE).head - return (modules as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_STREAM_LIST_SCHEMA_NODE).head - }else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE) { - return (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head - } else if (schemaNodeName == RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE) { - val modules = (restconfContainer as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULES_CONTAINER_SCHEMA_NODE).head - return (modules as DataNodeContainer).findInstanceDataChildrenByName(RESTCONF_MODULE_DRAFT02_MODULE_LIST_SCHEMA_NODE).head - } - return null - } - - override getRoot() { - return null; - } - - override invokeRpc(String identifier, CompositeNode payload) { - val rpc = resolveIdentifierInInvokeRpc(identifier) - if (rpc.QName.namespace.toString == SAL_REMOTE_NAMESPACE && rpc.QName.localName == SAL_REMOTE_RPC_SUBSRCIBE) { - val value = normalizeNode(payload, rpc.input, null) - val pathNode = value?.getFirstSimpleByName(QName.create(rpc.QName, "path")) - val pathValue = pathNode?.value - if (pathValue === null && !(pathValue instanceof InstanceIdentifier)) { - throw new ResponseException(INTERNAL_SERVER_ERROR, "Instance identifier was not normalized correctly."); - } - val pathIdentifier = (pathValue as InstanceIdentifier) - var String streamName = null - if (!pathIdentifier.path.nullOrEmpty) { - streamName = Notificator.createStreamNameFromUri(pathIdentifier.toFullRestconfIdentifier) - } - if (streamName.nullOrEmpty) { - throw new ResponseException(BAD_REQUEST, "Path is empty or contains data node which is not Container or List build-in type."); - } - val streamNameNode = NodeFactory.createImmutableSimpleNode(QName.create(rpc.output.QName, "stream-name"), null, streamName) - val List> output = new ArrayList - output.add(streamNameNode) - val responseData = NodeFactory.createMutableCompositeNode(rpc.output.QName, null, output, null, null) - - if (!Notificator.existListenerFor(pathIdentifier)) { - Notificator.createListener(pathIdentifier, streamName) - } - - return new StructuredData(responseData, rpc.output, null) - } - return callRpc(identifier.rpcDefinition, payload) - } - - override invokeRpc(String identifier, String noPayload) { - if (!noPayload.nullOrEmpty) { - throw new ResponseException(UNSUPPORTED_MEDIA_TYPE, "Content-Type contains unsupported Media Type."); - } - val rpc = resolveIdentifierInInvokeRpc(identifier) - return callRpc(rpc, null) - } - - private def resolveIdentifierInInvokeRpc(String identifier) { - if (identifier.indexOf("/") === -1) { - val identifierDecoded = identifier.urlPathArgDecode - val rpc = identifierDecoded.rpcDefinition - if (rpc !== null) { - return rpc - } - throw new ResponseException(NOT_FOUND, "RPC does not exist."); - } - val slashErrorMsg = String.format( - "Identifier %n%s%ncan't contain slash character (/).%nIf slash is part of identifier name then use %%2F placeholder.", identifier) - throw new ResponseException(NOT_FOUND, slashErrorMsg); - } - - private def StructuredData callRpc(RpcDefinition rpc, CompositeNode payload) { - if (rpc === null) { - throw new ResponseException(NOT_FOUND, "RPC does not exist."); - } - var CompositeNode rpcRequest; - if (payload === null) { - rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, null, null, null) - } else { - val value = normalizeNode(payload, rpc.input, null) - val List> input = new ArrayList - input.add(value) - rpcRequest = NodeFactory.createMutableCompositeNode(rpc.QName, null, input, null, null) - } - val rpcResult = broker.invokeRpc(rpc.QName, rpcRequest); - if (!rpcResult.successful) { - throw new ResponseException(INTERNAL_SERVER_ERROR, "Operation failed") - } - if (rpcResult.result === null) { - return null - } - return new StructuredData(rpcResult.result, rpc.output, null) - } - - override readConfigurationData(String identifier) { - val iiWithData = identifier.toInstanceIdentifier - var CompositeNode data = null; - if (iiWithData.mountPoint !== null) { - data = broker.readConfigurationDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier) - } else { - data = broker.readConfigurationData(iiWithData.getInstanceIdentifier); - } - return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint) - } - - override readOperationalData(String identifier) { - val iiWithData = identifier.toInstanceIdentifier - var CompositeNode data = null; - if (iiWithData.mountPoint !== null) { - data = broker.readOperationalDataBehindMountPoint(iiWithData.mountPoint, iiWithData.getInstanceIdentifier) - } else { - data = broker.readOperationalData(iiWithData.getInstanceIdentifier); - } - return new StructuredData(data, iiWithData.schemaNode, iiWithData.mountPoint) - } - - override updateConfigurationData(String identifier, CompositeNode payload) { - val iiWithData = identifier.toInstanceIdentifier - val value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint) - var RpcResult status = null - if (iiWithData.mountPoint !== null) { - status = broker.commitConfigurationDataPutBehindMountPoint(iiWithData.mountPoint, - iiWithData.instanceIdentifier, value).get() - } else { - status = broker.commitConfigurationDataPut(iiWithData.instanceIdentifier, value).get(); - } - switch status.result { - case TransactionStatus.COMMITED: Response.status(OK).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } - } - - override createConfigurationData(String identifier, CompositeNode payload) { - if (payload.namespace === null) { - throw new ResponseException(BAD_REQUEST, - "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); - } - var InstanceIdWithSchemaNode iiWithData; - var CompositeNode value; - if (payload.representsMountPointRootData) { // payload represents mount point data and URI represents path to the mount point - if (identifier.endsWithMountPoint) { - throw new ResponseException(BAD_REQUEST, - "URI has bad format. URI should be without \"" + ControllerContext.MOUNT + "\" for POST operation."); - } - val completIdentifier = identifier.addMountPointIdentifier - iiWithData = completIdentifier.toInstanceIdentifier - value = normalizeNode(payload, iiWithData.schemaNode, iiWithData.mountPoint) - } else { - val uncompleteInstIdWithData = identifier.toInstanceIdentifier - val parentSchema = uncompleteInstIdWithData.schemaNode as DataNodeContainer - val module = uncompleteInstIdWithData.mountPoint.findModule(payload) - if (module === null) { - throw new ResponseException(BAD_REQUEST, "Module was not found for \"" + payload.namespace + "\"") - } - val schemaNode = parentSchema.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace) - value = normalizeNode(payload, schemaNode, uncompleteInstIdWithData.mountPoint) - iiWithData = uncompleteInstIdWithData.addLastIdentifierFromData(value, schemaNode) - } - var RpcResult status = null - if (iiWithData.mountPoint !== null) { - status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint, - iiWithData.instanceIdentifier, value)?.get(); - } else { - status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get(); - } - if (status === null) { - return Response.status(ACCEPTED).build - } - switch status.result { - case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } - } - - override createConfigurationData(CompositeNode payload) { - if (payload.namespace === null) { - throw new ResponseException(BAD_REQUEST, - "Data has bad format. Root element node must have namespace (XML format) or module name(JSON format)"); - } - val module = findModule(null, payload) - if (module === null) { - throw new ResponseException(BAD_REQUEST, - "Data has bad format. Root element node has incorrect namespace (XML format) or module name(JSON format)"); - } - val schemaNode = module.findInstanceDataChildByNameAndNamespace(payload.name, module.namespace) - val value = normalizeNode(payload, schemaNode, null) - val iiWithData = addLastIdentifierFromData(null, value, schemaNode) - var RpcResult status = null - if (iiWithData.mountPoint !== null) { - status = broker.commitConfigurationDataPostBehindMountPoint(iiWithData.mountPoint, - iiWithData.instanceIdentifier, value)?.get(); - } else { - status = broker.commitConfigurationDataPost(iiWithData.instanceIdentifier, value)?.get(); - } - if (status === null) { - return Response.status(ACCEPTED).build - } - switch status.result { - case TransactionStatus.COMMITED: Response.status(NO_CONTENT).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } - } - - override deleteConfigurationData(String identifier) { - val iiWithData = identifier.toInstanceIdentifier - var RpcResult status = null - if (iiWithData.mountPoint !== null) { - status = broker.commitConfigurationDataDeleteBehindMountPoint(iiWithData.mountPoint, - iiWithData.getInstanceIdentifier).get; - } else { - status = broker.commitConfigurationDataDelete(iiWithData.getInstanceIdentifier).get; - } - switch status.result { - case TransactionStatus.COMMITED: Response.status(OK).build - default: Response.status(INTERNAL_SERVER_ERROR).build - } - } - - override subscribeToStream(String identifier, UriInfo uriInfo) { - val streamName = Notificator.createStreamNameFromUri(identifier) - if (streamName.nullOrEmpty) { - throw new ResponseException(BAD_REQUEST, "Stream name is empty.") - } - val listener = Notificator.getListenerFor(streamName); - if (listener === null) { - throw new ResponseException(BAD_REQUEST, "Stream was not found.") - } - broker.registerToListenDataChanges(listener) - val uriBuilder = uriInfo.getAbsolutePathBuilder() - val uriToWebsocketServer = uriBuilder.port(WebSocketServer.PORT).replacePath(streamName).build() - return Response.status(OK).location(uriToWebsocketServer).build - } - - private def dispatch URI namespace(CompositeNode data) { - return data.nodeType.namespace - } - - private def dispatch URI namespace(CompositeNodeWrapper data) { - return data.namespace - } - - private def dispatch String localName(CompositeNode data) { - return data.nodeType.localName - } - - private def dispatch String localName(CompositeNodeWrapper data) { - return data.localName - } - - private def dispatch Module findModule(MountInstance mountPoint, CompositeNode data) { - if (mountPoint !== null) { - return mountPoint.findModuleByNamespace(data.nodeType.namespace) - } else { - return findModuleByNamespace(data.nodeType.namespace) - } - } - - private def dispatch Module findModule(MountInstance mountPoint, CompositeNodeWrapper data) { - Preconditions.checkNotNull(data.namespace) - var Module module = null; - if (mountPoint !== null) { - module = mountPoint.findModuleByNamespace(data.namespace) // namespace from XML - if (module === null) { - module = mountPoint.findModuleByName(data.namespace.toString) // namespace (module name) from JSON - } - } else { - module = data.namespace.findModuleByNamespace // namespace from XML - if (module === null) { - module = data.namespace.toString.findModuleByName // namespace (module name) from JSON - } - } - return module - } - - private def dispatch getName(CompositeNode data) { - return data.nodeType.localName - } - - private def dispatch getName(CompositeNodeWrapper data) { - return data.localName - } - - private def InstanceIdWithSchemaNode addLastIdentifierFromData(InstanceIdWithSchemaNode identifierWithSchemaNode, - CompositeNode data, DataSchemaNode schemaOfData) { - val iiOriginal = identifierWithSchemaNode?.instanceIdentifier - var InstanceIdentifierBuilder iiBuilder = null - if (iiOriginal === null) { - iiBuilder = InstanceIdentifier.builder - } else { - iiBuilder = InstanceIdentifier.builder(iiOriginal) - } - - if (schemaOfData instanceof ListSchemaNode) { - iiBuilder.nodeWithKey(schemaOfData.QName, (schemaOfData as ListSchemaNode).resolveKeysFromData(data)) - } else { - iiBuilder.node(schemaOfData.QName) - } - return new InstanceIdWithSchemaNode(iiBuilder.toInstance, schemaOfData, identifierWithSchemaNode?.mountPoint) - } - - private def resolveKeysFromData(ListSchemaNode listNode, CompositeNode dataNode) { - val keyValues = new HashMap(); - for (key : listNode.keyDefinition) { - val dataNodeKeyValueObject = dataNode.getSimpleNodesByName(key.localName)?.head?.value - if (dataNodeKeyValueObject === null) { - throw new ResponseException(BAD_REQUEST, - "Data contains list \"" + dataNode.nodeType.localName + "\" which does not contain key: \"" + - key.localName + "\"") - } - keyValues.put(key, dataNodeKeyValueObject); - } - return keyValues - } - - private def endsWithMountPoint(String identifier) { - return (identifier.endsWith(ControllerContext.MOUNT) || identifier.endsWith(ControllerContext.MOUNT + "/")) - } - - private def representsMountPointRootData(CompositeNode data) { - return ((data.namespace == SchemaContext.NAME.namespace || data.namespace == MOUNT_POINT_MODULE_NAME) && - data.localName == SchemaContext.NAME.localName) - } - - private def addMountPointIdentifier(String identifier) { - if (identifier.endsWith("/")) { - return identifier + ControllerContext.MOUNT - } - return identifier + "/" + ControllerContext.MOUNT - } - - private def CompositeNode normalizeNode(CompositeNode node, DataSchemaNode schema, MountInstance mountPoint) { - if (schema === null) { - throw new ResponseException(INTERNAL_SERVER_ERROR, - "Data schema node was not found for " + node?.nodeType?.localName) - } - if (!(schema instanceof DataNodeContainer)) { - throw new ResponseException(BAD_REQUEST, "Root element has to be container or list yang datatype."); - } - if (node instanceof CompositeNodeWrapper) { - if ((node as CompositeNodeWrapper).changeAllowed) { - try { - normalizeNode(node as CompositeNodeWrapper, schema, null, mountPoint) - } catch (NumberFormatException e) { - throw new ResponseException(BAD_REQUEST,e.message) - } - } - return (node as CompositeNodeWrapper).unwrap() - } - return node - } - - private def void normalizeNode(NodeWrapper nodeBuilder, DataSchemaNode schema, QName previousAugment, - MountInstance mountPoint) { - if (schema === null) { - throw new ResponseException(BAD_REQUEST, - "Data has bad format.\n\"" + nodeBuilder.localName + "\" does not exist in yang schema."); - } - - var QName currentAugment; - if (nodeBuilder.qname !== null) { - currentAugment = previousAugment - } else { - currentAugment = normalizeNodeName(nodeBuilder, schema, previousAugment, mountPoint) - if (nodeBuilder.qname === null) { - throw new ResponseException(BAD_REQUEST, - "Data has bad format.\nIf data is in XML format then namespace for \"" + nodeBuilder.localName + - "\" should be \"" + schema.QName.namespace + "\".\n" + - "If data is in JSON format then module name for \"" + nodeBuilder.localName + - "\" should be corresponding to namespace \"" + schema.QName.namespace + "\"."); - } - } - - if (nodeBuilder instanceof CompositeNodeWrapper) { - val List> children = (nodeBuilder as CompositeNodeWrapper).getValues - for (child : children) { - val potentialSchemaNodes = (schema as DataNodeContainer).findInstanceDataChildrenByName(child.localName) - if (potentialSchemaNodes.size > 1 && child.namespace === null) { - val StringBuilder namespacesOfPotentialModules = new StringBuilder; - for (potentialSchemaNode : potentialSchemaNodes) { - namespacesOfPotentialModules.append(" ").append(potentialSchemaNode.QName.namespace.toString).append("\n") - } - throw new ResponseException(BAD_REQUEST, - "Node \"" + child.localName + "\" is added as augment from more than one module. " - + "Therefore node must have namespace (XML format) or module name (JSON format)." - + "\nThe node is added as augment from modules with namespaces:\n" + namespacesOfPotentialModules) - } - var rightNodeSchemaFound = false - for (potentialSchemaNode : potentialSchemaNodes) { - if (!rightNodeSchemaFound) { - val potentialCurrentAugment = normalizeNodeName(child, potentialSchemaNode, currentAugment, - mountPoint) - if (child.qname !== null) { - normalizeNode(child, potentialSchemaNode, potentialCurrentAugment, mountPoint) - rightNodeSchemaFound = true - } - } - } - if (!rightNodeSchemaFound) { - throw new ResponseException(BAD_REQUEST, - "Schema node \"" + child.localName + "\" was not found in module.") - } - } - if (schema instanceof ListSchemaNode) { - val listKeys = (schema as ListSchemaNode).keyDefinition - for (listKey : listKeys) { - var foundKey = false - for (child : children) { - if (child.unwrap.nodeType.localName == listKey.localName) { - foundKey = true; - } - } - if (!foundKey) { - throw new ResponseException(BAD_REQUEST, - "Missing key in URI \"" + listKey.localName + "\" of list \"" + schema.QName.localName + - "\"") - } - } - } - } else if (nodeBuilder instanceof SimpleNodeWrapper) { - val simpleNode = (nodeBuilder as SimpleNodeWrapper) - val value = simpleNode.value - var inputValue = value; - - if (schema.typeDefinition instanceof IdentityrefTypeDefinition) { - if (value instanceof String) { - inputValue = new IdentityValuesDTO(nodeBuilder.namespace.toString, value as String, null,value as String); - } // else value is already instance of IdentityValuesDTO - } - - val outputValue = RestCodec.from(schema.typeDefinition, mountPoint)?.deserialize(inputValue); - simpleNode.setValue(outputValue) - } else if (nodeBuilder instanceof EmptyNodeWrapper) { - val emptyNodeBuilder = nodeBuilder as EmptyNodeWrapper - if (schema instanceof LeafSchemaNode) { - emptyNodeBuilder.setComposite(false); - } else if (schema instanceof ContainerSchemaNode) { - - // FIXME: Add presence check - emptyNodeBuilder.setComposite(true); - } - } - } - - private def dispatch TypeDefinition typeDefinition(LeafSchemaNode node) { - var baseType = node.type - while (baseType.baseType !== null) { - baseType = baseType.baseType; - } - baseType - } - - private def dispatch TypeDefinition typeDefinition(LeafListSchemaNode node) { - var TypeDefinition baseType = node.type - while (baseType.baseType !== null) { - baseType = baseType.baseType; - } - baseType - } - - private def QName normalizeNodeName(NodeWrapper nodeBuilder, DataSchemaNode schema, QName previousAugment, - MountInstance mountPoint) { - var validQName = schema.QName - var currentAugment = previousAugment; - if (schema.augmenting) { - currentAugment = schema.QName - } else if (previousAugment !== null && schema.QName.namespace !== previousAugment.namespace) { - validQName = QName.create(currentAugment, schema.QName.localName); - } - var String moduleName = null; - if (mountPoint === null) { - moduleName = controllerContext.findModuleNameByNamespace(validQName.namespace); - } else { - moduleName = controllerContext.findModuleNameByNamespace(mountPoint, validQName.namespace) - } - if (nodeBuilder.namespace === null || nodeBuilder.namespace == validQName.namespace || - nodeBuilder.namespace.toString == moduleName || nodeBuilder.namespace == MOUNT_POINT_MODULE_NAME) { - nodeBuilder.qname = validQName - } - return currentAugment - } - -} diff --git a/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java new file mode 100644 index 0000000000..987beb072b --- /dev/null +++ b/opendaylight/md-sal/sal-rest-connector/src/test/java/org/opendaylight/controller/sal/restconf/impl/test/BrokerFacadeTest.java @@ -0,0 +1,327 @@ +/* + * Copyright (c) ${year} Brocade Communications Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.controller.sal.restconf.impl.test; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.Future; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.opendaylight.controller.md.sal.common.api.TransactionStatus; +import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession; +import org.opendaylight.controller.sal.core.api.data.DataBrokerService; +import org.opendaylight.controller.sal.core.api.data.DataChangeListener; +import org.opendaylight.controller.sal.core.api.data.DataModificationTransaction; +import org.opendaylight.controller.sal.core.api.mount.MountInstance; +import org.opendaylight.controller.sal.rest.impl.XmlToCompositeNodeProvider; +import org.opendaylight.controller.sal.restconf.impl.BrokerFacade; +import org.opendaylight.controller.sal.restconf.impl.ResponseException; +import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter; +import org.opendaylight.controller.sal.streams.listeners.Notificator; +import org.opendaylight.yangtools.concepts.ListenerRegistration; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.common.RpcResult; +import org.opendaylight.yangtools.yang.data.api.CompositeNode; +import org.opendaylight.yangtools.yang.data.api.InstanceIdentifier; + +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; + +/** + * Unit tests for BrokerFacade. + * + * @author Thomas Pantelis + */ +public class BrokerFacadeTest { + + @Mock + DataBrokerService dataBroker; + + @Mock + DataModificationTransaction mockTransaction; + + @Mock + ConsumerSession mockConsumerSession; + + @Mock + MountInstance mockMountInstance; + + BrokerFacade brokerFacade = BrokerFacade.getInstance(); + + CompositeNode dataNode = TestUtils.readInputToCnSn( "/parts/ietf-interfaces_interfaces.xml", + XmlToCompositeNodeProvider.INSTANCE ); + + QName qname = QName.create( "node" ); + + InstanceIdentifier instanceID = InstanceIdentifier.builder().node( qname ).toInstance(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks( this ); + + brokerFacade.setDataService( dataBroker ); + brokerFacade.setContext( mockConsumerSession ); + } + + @Test + public void testReadConfigurationData() { + when( dataBroker.readConfigurationData( instanceID ) ).thenReturn( dataNode ); + + CompositeNode actualNode = brokerFacade.readConfigurationData( instanceID ); + + assertSame( "readConfigurationData", dataNode, actualNode ); + } + + @Test + public void testReadConfigurationDataBehindMountPoint() { + when( mockMountInstance.readConfigurationData( instanceID ) ).thenReturn( dataNode ); + + CompositeNode actualNode = brokerFacade.readConfigurationDataBehindMountPoint( + mockMountInstance, instanceID ); + + assertSame( "readConfigurationDataBehindMountPoint", dataNode, actualNode ); + } + + @Test + public void testReadOperationalData() { + when( dataBroker.readOperationalData( instanceID ) ).thenReturn( dataNode ); + + CompositeNode actualNode = brokerFacade.readOperationalData( instanceID ); + + assertSame( "readOperationalData", dataNode, actualNode ); + } + + @Test + public void testReadOperationalDataBehindMountPoint() { + when( mockMountInstance.readOperationalData( instanceID ) ).thenReturn( dataNode ); + + CompositeNode actualNode = brokerFacade.readOperationalDataBehindMountPoint( + mockMountInstance, instanceID ); + + assertSame( "readOperationalDataBehindMountPoint", dataNode, actualNode ); + } + + @Test(expected=ResponseException.class) + public void testReadOperationalDataWithNoDataBroker() { + brokerFacade.setDataService( null ); + + brokerFacade.readOperationalData( instanceID ); + } + + @SuppressWarnings("unchecked") + @Test + public void testInvokeRpc() { + RpcResult expResult = mock( RpcResult.class ); + Future> future = Futures.immediateFuture( expResult ); + when( mockConsumerSession.rpc( qname, dataNode ) ).thenReturn( future ); + + RpcResult actualResult = brokerFacade.invokeRpc( qname, dataNode ); + + assertSame( "invokeRpc", expResult, actualResult ); + } + + @Test(expected=ResponseException.class) + public void testInvokeRpcWithException() { + Exception mockEx = new Exception( "mock" ); + Future> future = Futures.immediateFailedFuture( mockEx ); + when( mockConsumerSession.rpc( qname, dataNode ) ).thenReturn( future ); + + brokerFacade.invokeRpc( qname, dataNode ); + } + + @Test(expected=ResponseException.class) + public void testInvokeRpcWithNoConsumerSession() { + brokerFacade.setContext( null ); + + brokerFacade.invokeRpc( qname, dataNode ); + } + + @Test + public void testCommitConfigurationDataPut() { + Future> expFuture = Futures.immediateFuture( null ); + + when( dataBroker.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPut( instanceID, dataNode ); + + assertSame( "invokeRpc", expFuture, actualFuture ); + + InOrder inOrder = inOrder( dataBroker, mockTransaction ); + inOrder.verify( dataBroker ).beginTransaction(); + inOrder.verify( mockTransaction ).putConfigurationData( instanceID, dataNode ); + inOrder.verify( mockTransaction ).commit(); + } + + @Test + public void testCommitConfigurationDataPutBehindMountPoint() { + Future> expFuture = Futures.immediateFuture( null ); + + when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPutBehindMountPoint( + mockMountInstance, instanceID, dataNode ); + + assertSame( "invokeRpc", expFuture, actualFuture ); + + InOrder inOrder = inOrder( mockMountInstance, mockTransaction ); + inOrder.verify( mockMountInstance ).beginTransaction(); + inOrder.verify( mockTransaction ).putConfigurationData( instanceID, dataNode ); + inOrder.verify( mockTransaction ).commit(); + } + + @Test + public void testCommitConfigurationDataPost() { + Future> expFuture = Futures.immediateFuture( null ); + + Map nodeMap = + new ImmutableMap.Builder() + .put( instanceID, dataNode ).build(); + + when( dataBroker.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.getCreatedConfigurationData() ).thenReturn( nodeMap ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPost( instanceID, dataNode ); + + assertSame( "commitConfigurationDataPut", expFuture, actualFuture ); + + InOrder inOrder = inOrder( dataBroker, mockTransaction ); + inOrder.verify( dataBroker ).beginTransaction(); + inOrder.verify( mockTransaction ).putConfigurationData( instanceID, dataNode ); + inOrder.verify( mockTransaction ).commit(); + } + + @Test + public void testCommitConfigurationDataPostAlreadyExists() { + when( dataBroker.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.getCreatedConfigurationData() ) + .thenReturn( Collections.emptyMap() ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPost( instanceID, dataNode ); + + assertNull( "Retruned non-null Future", actualFuture ); + verify( mockTransaction, never() ).commit(); + } + + @Test + public void testCommitConfigurationDataPostBehindMountPoint() { + Future> expFuture = Futures.immediateFuture( null ); + + Map nodeMap = + new ImmutableMap.Builder() + .put( instanceID, dataNode ).build(); + + when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.getCreatedConfigurationData() ).thenReturn( nodeMap ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPostBehindMountPoint( mockMountInstance, + instanceID, dataNode ); + + assertSame( "commitConfigurationDataPostBehindMountPoint", expFuture, actualFuture ); + + InOrder inOrder = inOrder( mockMountInstance, mockTransaction ); + inOrder.verify( mockMountInstance ).beginTransaction(); + inOrder.verify( mockTransaction ).putConfigurationData( instanceID, dataNode ); + inOrder.verify( mockTransaction ).commit(); + } + + @Test + public void testCommitConfigurationDataPostBehindMountPointAlreadyExists() { + + when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.putConfigurationData( instanceID, dataNode ); + when( mockTransaction.getCreatedConfigurationData() ) + .thenReturn( Collections.emptyMap() ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataPostBehindMountPoint( mockMountInstance, + instanceID, dataNode ); + + assertNull( "Retruned non-null Future", actualFuture ); + verify( mockTransaction, never() ).commit(); + } + + @Test + public void testCommitConfigurationDataDelete() { + Future> expFuture = Futures.immediateFuture( null ); + + when( dataBroker.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.removeConfigurationData( instanceID ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataDelete( instanceID ); + + assertSame( "commitConfigurationDataDelete", expFuture, actualFuture ); + + InOrder inOrder = inOrder( dataBroker, mockTransaction ); + inOrder.verify( dataBroker ).beginTransaction(); + inOrder.verify( mockTransaction ).removeConfigurationData( instanceID ); + inOrder.verify( mockTransaction ).commit(); + } + + @Test + public void testCommitConfigurationDataDeleteBehindMountPoint() { + Future> expFuture = Futures.immediateFuture( null ); + + when( mockMountInstance.beginTransaction() ).thenReturn( mockTransaction ); + mockTransaction.removeConfigurationData( instanceID ); + when( mockTransaction.commit() ).thenReturn( expFuture ); + + Future> actualFuture = + brokerFacade.commitConfigurationDataDeleteBehindMountPoint( + mockMountInstance, instanceID ); + + assertSame( "commitConfigurationDataDeleteBehindMountPoint", expFuture, actualFuture ); + + InOrder inOrder = inOrder( mockMountInstance, mockTransaction ); + inOrder.verify( mockMountInstance ).beginTransaction(); + inOrder.verify( mockTransaction ).removeConfigurationData( instanceID ); + inOrder.verify( mockTransaction ).commit(); + } + + @SuppressWarnings("unchecked") + @Test + public void testRegisterToListenDataChanges() { + ListenerAdapter listener = Notificator.createListener( instanceID, "stream" ); + + ListenerRegistration mockRegistration = mock( ListenerRegistration.class ); + when( dataBroker.registerDataChangeListener( instanceID, listener ) ) + .thenReturn( mockRegistration ); + + brokerFacade.registerToListenDataChanges( listener ); + + verify( dataBroker ).registerDataChangeListener( instanceID, listener ); + + assertEquals( "isListening", true, listener.isListening() ); + + brokerFacade.registerToListenDataChanges( listener ); + verifyNoMoreInteractions( dataBroker ); + } +} -- 2.36.6