Merge "Specify type to NetconfClientDispatcher reference"
authorTomas Cere <tcere@cisco.com>
Mon, 7 Nov 2016 13:06:52 +0000 (13:06 +0000)
committerGerrit Code Review <gerrit@opendaylight.org>
Mon, 7 Nov 2016 13:06:52 +0000 (13:06 +0000)
42 files changed:
netconf/netconf-impl/pom.xml
netconf/netconf-impl/src/main/java/org/opendaylight/netconf/impl/osgi/NetconfImplActivator.java
netconf/netconf-ssh/src/main/java/org/opendaylight/netconf/ssh/osgi/NetconfSSHActivator.java
netconf/netconf-ssh/src/test/java/org/opendaylight/netconf/netty/EchoServer.java
netconf/netconf-ssh/src/test/java/org/opendaylight/netconf/netty/ProxyServer.java
netconf/netconf-ssh/src/test/java/org/opendaylight/netconf/netty/SSHTest.java
netconf/netconf-ssh/src/test/java/org/opendaylight/netconf/ssh/authentication/SSHServerTest.java
netconf/netconf-tcp/src/main/java/org/opendaylight/netconf/tcp/osgi/NetconfTCPActivator.java
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/osgi/NetconfConfigUtil.java
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/osgi/NetconfConfiguration.java
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationActivator.java
netconf/netconf-util/src/main/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationHolder.java
netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigUtilTest.java
netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationActivatorTest.java [new file with mode: 0644]
netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationTest.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/NetconfDeviceDataBroker.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/AbstractWriteTx.java
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChain.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxListener.java [new file with mode: 0644]
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/NetconfDeviceWriteOnlyTxTest.java
netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChainTest.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/NetconfDeviceSimulator.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/TesttoolParameters.java
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java [new file with mode: 0644]
netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java [new file with mode: 0644]
restconf/sal-rest-docgen/pom.xml
restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ApiDocGenerator.java
restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ApiDocServiceImpl.java
restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/BaseYangSwaggerGenerator.java
restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/impl/ModelGenerator.java
restconf/sal-rest-docgen/src/main/java/org/opendaylight/netconf/sal/rest/doc/mountpoints/MountPointSwagger.java
restconf/sal-rest-docgen/src/main/resources/17/explorer/index.html [new file with mode: 0644]
restconf/sal-rest-docgen/src/main/resources/17/explorer/lib/odl/list_mounts.js [new file with mode: 0644]
restconf/sal-rest-docgen/src/main/resources/17/explorer/static/index.html [new file with mode: 0644]
restconf/sal-rest-docgen/src/main/resources/README.txt
restconf/sal-rest-docgen/src/main/resources/WEB-INF/web.xml
restconf/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/ApiDocGeneratorTest.java
restconf/sal-rest-docgen/src/test/java/org/opendaylight/controller/sal/rest/doc/impl/MountPointSwaggerTest.java

index a4ff368ca7215bacbed181196842c6859890167a..c78eb147a937f2a6aa6eae4c7eb8f57196e6de27 100644 (file)
       <artifactId>xmlunit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
index 9d520bea56b82da45857f9e4029c24882d69762c..8feaef6aedc9366381d35cea1a9cc78a8599edf0 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactoryListen
 import org.opendaylight.netconf.notifications.BaseNotificationPublisherRegistration;
 import org.opendaylight.netconf.notifications.NetconfNotificationCollector;
 import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
@@ -52,7 +53,8 @@ public class NetconfImplActivator implements BundleActivator {
 
             SessionIdProvider idProvider = new SessionIdProvider();
             timer = new HashedWheelTimer();
-            long connectionTimeoutMillis = NetconfConfigUtil.extractTimeoutMillis(context);
+
+            long connectionTimeoutMillis = NetconfConfiguration.DEFAULT_TIMEOUT_MILLIS;
 
             final NetconfMonitoringServiceImpl monitoringService = startMonitoringService(context, factoriesListener);
 
@@ -70,7 +72,7 @@ public class NetconfImplActivator implements BundleActivator {
                     serverNegotiatorFactory);
             NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, eventLoopGroup, eventLoopGroup);
 
-            LocalAddress address = NetconfConfigUtil.getNetconfLocalAddress();
+            LocalAddress address = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
             LOG.trace("Starting local netconf server at {}", address);
             dispatch.createLocalServer(address);
 
index 14cbae81e1891edb357717c5f0dfc88e88dcd545..537ea993d1b113b5b46d76b06fd777ed13dca151 100644 (file)
@@ -84,7 +84,7 @@ public class NetconfSSHActivator implements BundleActivator {
         final InetSocketAddress sshSocketAddress = netconfConfiguration.getSshServerAddress();
         LOG.info("Starting netconf SSH server at {}", sshSocketAddress);
 
-        final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+        final LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
         authProviderTracker = new AuthProviderTracker(bundleContext);
 
         final String path = netconfConfiguration.getPrivateKeyPath();
index 53a54dc601ef6525c5d3aa6cb638b7a39100352c..a6f9e6cb66595bd0616c35774684571fe683a11b 100644 (file)
@@ -21,7 +21,7 @@ import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -49,7 +49,7 @@ public class EchoServer implements Runnable {
                     });
 
             // Start the server.
-            LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
             ChannelFuture f = b.bind(localAddress).sync();
 
             // Wait until the server socket is closed.
index 8294a35c954b3326f42c87d27466907f83aec515..2d9b67d42dd7dd2d54759747f2d61a9767f31935 100644 (file)
@@ -22,6 +22,7 @@ import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 import java.net.InetSocketAddress;
 import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 
 public class ProxyServer implements Runnable {
     private final ProxyHandlerFactory proxyHandlerFactory;
@@ -35,7 +36,7 @@ public class ProxyServer implements Runnable {
         final EventLoopGroup bossGroup = new NioEventLoopGroup();
         EventLoopGroup workerGroup = new NioEventLoopGroup();
         try {
-            final LocalAddress localAddress = NetconfConfigUtil.getNetconfLocalAddress();
+            final LocalAddress localAddress = NetconfConfiguration.NETCONF_LOCAL_ADDRESS;
             ServerBootstrap serverBootstrap = new ServerBootstrap();
             serverBootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
index b7379808cf3d941374abd6b4f025b59c3243771e..759b96210bdd068c05f1a6309b223c3d026e40ab 100644 (file)
@@ -36,7 +36,7 @@ import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswo
 import org.opendaylight.netconf.nettyutil.handler.ssh.client.AsyncSshHandler;
 import org.opendaylight.netconf.ssh.SshProxyServer;
 import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,7 +74,7 @@ public class SSHTest {
         final InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 10831);
         final SshProxyServer sshProxyServer = new SshProxyServer(minaTimerEx, nettyGroup, nioExec);
         sshProxyServer.bind(
-                new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfigUtil.getNetconfLocalAddress()).setAuthenticator(new AuthProvider() {
+                new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfiguration.NETCONF_LOCAL_ADDRESS).setAuthenticator(new AuthProvider() {
                     @Override
                     public boolean authenticated(final String username, final String password) {
                         return true;
index f0350247187a925132de8aae7fab374d863bc0ca..7c7ef6383d9882e1ae5887b73688d00062e6238a 100644 (file)
@@ -34,6 +34,7 @@ import org.opendaylight.netconf.auth.AuthProvider;
 import org.opendaylight.netconf.ssh.SshProxyServer;
 import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
 import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
+import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
@@ -73,7 +74,7 @@ public class SSHServerTest {
         final InetSocketAddress addr = InetSocketAddress.createUnresolved(HOST, PORT);
         server = new SshProxyServer(minaTimerEx, clientGroup, nioExec);
         server.bind(
-                new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfigUtil.getNetconfLocalAddress()).setAuthenticator(new AuthProvider() {
+                new SshProxyServerConfigurationBuilder().setBindingAddress(addr).setLocalAddress(NetconfConfiguration.NETCONF_LOCAL_ADDRESS).setAuthenticator(new AuthProvider() {
                     @Override
                     public boolean authenticated(final String username, final String password) {
                         return true;
index d972a32ba70c9ede29a146cdc572bd610f979862..2d6798cfb75298f8b7c6d77d40c0e8c6274e2ef6 100644 (file)
@@ -11,7 +11,6 @@ package org.opendaylight.netconf.tcp.osgi;
 import java.net.InetSocketAddress;
 import org.opendaylight.netconf.tcp.netty.ProxyServer;
 import org.opendaylight.netconf.util.osgi.NetconfConfigUtil;
-import org.opendaylight.netconf.util.osgi.NetconfConfigUtil.InfixProp;
 import org.opendaylight.netconf.util.osgi.NetconfConfiguration;
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -33,11 +32,11 @@ public class NetconfTCPActivator implements BundleActivator {
         final InetSocketAddress address = netconfConfiguration.getTcpServerAddress();
 
         if (address.getAddress().isAnyLocalAddress()) {
-            LOG.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. Consider changing {} to 127.0.0.1",
-                    NetconfConfigUtil.getNetconfServerAddressKey(InfixProp.tcp));
+            LOG.warn("Unprotected netconf TCP address is configured to ANY local address. This is a security risk. " +
+                            "Consider changing tcp-address in netconf.cfg to 127.0.0.1");
         }
         LOG.info("Starting TCP netconf server at {}", address);
-        proxyServer = new ProxyServer(address, NetconfConfigUtil.getNetconfLocalAddress());
+        proxyServer = new ProxyServer(address, NetconfConfiguration.NETCONF_LOCAL_ADDRESS);
     }
 
     @Override
index 9d4b5a123839fa39eaa39a119560dc116deb7cb3..63bec7c525362593537c2dd2b48824ec73f15613 100644 (file)
@@ -8,11 +8,8 @@
 
 package org.opendaylight.netconf.util.osgi;
 
-import com.google.common.base.Optional;
-import io.netty.channel.local.LocalAddress;
-import java.net.InetSocketAddress;
 import java.util.Collection;
-import java.util.concurrent.TimeUnit;
+import java.util.Optional;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
@@ -23,111 +20,23 @@ import org.slf4j.LoggerFactory;
 public final class NetconfConfigUtil {
     private static final Logger LOG = LoggerFactory.getLogger(NetconfConfigUtil.class);
 
-    private static final String PREFIX_PROP = "netconf.";
-
     private NetconfConfigUtil() {
     }
 
-    public enum InfixProp {
-        tcp, ssh
-    }
-
-    private static final String PORT_SUFFIX_PROP = ".port";
-    private static final String ADDRESS_SUFFIX_PROP = ".address";
-    private static final String PRIVATE_KEY_PATH_PROP = ".pk.path";
-
-    private static final String CONNECTION_TIMEOUT_MILLIS_PROP = "connectionTimeoutMillis";
-    private static final String LOCAL_HOST = "127.0.0.1";
-    private static final String INADDR_ANY = "0.0.0.0";
-    public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
-    private static final LocalAddress NETCONF_LOCAL_ADDRESS = new LocalAddress("netconf");
-    public static final String DEFAULT_PRIVATE_KEY_PATH = "./configuration/RSA.pk";
-    public static final InetSocketAddress DEFAULT_TCP_SERVER_ADRESS = new InetSocketAddress(LOCAL_HOST, 8383);
-    public static final InetSocketAddress DEFAULT_SSH_SERVER_ADRESS = new InetSocketAddress(INADDR_ANY, 1830);
-
-    public static LocalAddress getNetconfLocalAddress() {
-        return NETCONF_LOCAL_ADDRESS;
-    }
-
-    public static long extractTimeoutMillis(final BundleContext bundleContext) {
-        final String key = PREFIX_PROP + CONNECTION_TIMEOUT_MILLIS_PROP;
-        final String timeoutString = bundleContext.getProperty(key);
-        if (timeoutString == null || timeoutString.length() == 0) {
-            return DEFAULT_TIMEOUT_MILLIS;
-        }
-        try {
-            return Long.parseLong(timeoutString);
-        } catch (final NumberFormatException e) {
-            LOG.warn("Cannot parse {} property: {}, using defaults", key, timeoutString, e);
-            return DEFAULT_TIMEOUT_MILLIS;
-        }
-    }
-
-    /**
-     * @param context from which properties are being read.
-     * @return value of private key path if value is present, Optional.absent otherwise
-     */
-    public static Optional<String> getPrivateKeyPath(final BundleContext context) {
-        return getProperty(context, getPrivateKeyKey());
-    }
-
-    public static String getPrivateKeyKey() {
-        return PREFIX_PROP + InfixProp.ssh + PRIVATE_KEY_PATH_PROP;
-    }
-
-    public static String getNetconfServerAddressKey(final InfixProp infixProp) {
-        return PREFIX_PROP + infixProp + ADDRESS_SUFFIX_PROP;
-    }
-
-    /**
-     * @param context   from which properties are being read.
-     * @param infixProp either tcp or ssh
-     * @return value if address and port are present and valid, Optional.absent otherwise.
-     */
-    public static Optional<InetSocketAddress> extractNetconfServerAddress(final BundleContext context,
-                                                                           final InfixProp infixProp) {
-
-        final Optional<String> address = getProperty(context, getNetconfServerAddressKey(infixProp));
-        final Optional<String> port = getProperty(context, PREFIX_PROP + infixProp + PORT_SUFFIX_PROP);
-
-        if (address.isPresent() && port.isPresent()) {
-            try {
-                return Optional.of(parseAddress(address, port));
-            } catch (final IllegalArgumentException | SecurityException e) {
-                LOG.warn("Unable to parse {} netconf address from {}:{}, fallback to default",
-                        infixProp, address, port, e);
-            }
-        }
-        return Optional.absent();
-    }
-
-    private static InetSocketAddress parseAddress(final Optional<String> address, final Optional<String> port) {
-        final int portNumber = Integer.valueOf(port.get());
-        return new InetSocketAddress(address.get(), portNumber);
-    }
-
-    private static Optional<String> getProperty(final BundleContext context, final String propKey) {
-        String value = context.getProperty(propKey);
-        if (value != null && value.isEmpty()) {
-            value = null;
-        }
-        return Optional.fromNullable(value);
-    }
-
-    public static java.util.Optional<NetconfConfiguration> getNetconfConfigurationService(BundleContext bundleContext) {
+    public static Optional<NetconfConfiguration> getNetconfConfigurationService(BundleContext bundleContext) {
         final Collection<ServiceReference<ManagedService>> serviceReferences;
         try {
             serviceReferences = bundleContext.getServiceReferences(ManagedService.class, null);
             for (final ServiceReference<ManagedService> serviceReference : serviceReferences) {
                 ManagedService service = bundleContext.getService(serviceReference);
                 if (service instanceof NetconfConfiguration){
-                    return java.util.Optional.of((NetconfConfiguration) service);
+                    return Optional.of((NetconfConfiguration) service);
                 }
             }
         } catch (InvalidSyntaxException e) {
             LOG.error("Unable to retrieve references for ManagedService: {}", e);
         }
-        LOG.error("Unable to retrieve NetconfConfiguration service. Not found. Bundle netconf-util probably failed.");
-        return java.util.Optional.empty();
+        LOG.error("Unable to retrieve NetconfConfiguration service. Not found. Bundle netconf-util probably failed to load.");
+        return Optional.empty();
     }
 }
index e33f7e2e71fa55c0f0a10515a9c54bea4d610f97..bf935dee867906e0889997a0f7bdea142321d9a2 100644 (file)
@@ -8,8 +8,10 @@
 
 package org.opendaylight.netconf.util.osgi;
 
+import io.netty.channel.local.LocalAddress;
 import java.net.InetSocketAddress;
 import java.util.Dictionary;
+import java.util.concurrent.TimeUnit;
 import org.osgi.service.cm.ManagedService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -17,39 +19,61 @@ import org.slf4j.LoggerFactory;
 public class NetconfConfiguration implements ManagedService {
     private static final Logger LOG = LoggerFactory.getLogger(NetconfConfiguration.class);
 
-    private static final NetconfConfiguration instance = new NetconfConfiguration();
-    private NetconfConfigurationHolder netconfConfiguration;
+    /**
+     * Props to access information within the dictionary.
+     */
+
+    private static final String SSH_ADDRESS_PROP = "ssh-address";
+    private static final String SSH_PORT_PROP = "ssh-port";
+    private static final String TCP_ADDRESS_PROP = "tcp-address";
+    private static final String TCP_PORT_PROP = "tcp-port";
+    private static final String SSH_PK_PATH_PROP = "ssh-pk-path";
+
+    /**
+     * Default values used if no dictionary is provided.
+     */
+
+    public static final LocalAddress NETCONF_LOCAL_ADDRESS = new LocalAddress("netconf");
+    public static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
 
-    public static final String KEY_SSH_ADDRESS = "ssh-address";
-    public static final String KEY_SSH_PORT = "ssh-port";
-    public static final String KEY_TCP_ADDRESS = "tcp-address";
-    public static final String KEY_TCP_PORT = "tcp-port";
-    public static final String KEY_SSH_PK_PATH = "ssh-pk-path";
+    private static final String LOCAL_HOST = "127.0.0.1";
+    private static final String INADDR_ANY = "0.0.0.0";
+    private static final String DEFAULT_PRIVATE_KEY_PATH = "./configuration/RSA.pk";
+    private static final InetSocketAddress DEFAULT_TCP_SERVER_ADRESS = new InetSocketAddress(LOCAL_HOST, 8383);
+    private static final InetSocketAddress DEFAULT_SSH_SERVER_ADRESS = new InetSocketAddress(INADDR_ANY, 1830);
+
+    /**
+     * Singleton instance of this class, registered as a service in the OSGi registry.
+     */
+    private static final NetconfConfiguration INSTANCE = new NetconfConfiguration();
+
+    private NetconfConfigurationHolder netconfConfiguration;
 
     public static NetconfConfiguration getInstance() {
-        return instance;
+        return INSTANCE;
     }
 
     private NetconfConfiguration() {
-        netconfConfiguration = new NetconfConfigurationHolder(NetconfConfigUtil.DEFAULT_TCP_SERVER_ADRESS,
-                NetconfConfigUtil.DEFAULT_SSH_SERVER_ADRESS, NetconfConfigUtil.DEFAULT_PRIVATE_KEY_PATH);
+        netconfConfiguration = new NetconfConfigurationHolder(DEFAULT_TCP_SERVER_ADRESS,
+                DEFAULT_SSH_SERVER_ADRESS, DEFAULT_PRIVATE_KEY_PATH);
     }
 
     @Override
     public void updated(final Dictionary<String, ?> dictionaryConfig) {
         if (dictionaryConfig == null) {
-            LOG.warn("Netconf configuration cannot be updated.");
+            LOG.trace("CSS netconf server configuration cannot be updated as passed dictionary is null");
             return;
         }
-        final InetSocketAddress sshServerAddress = new InetSocketAddress((String) dictionaryConfig.get(KEY_SSH_ADDRESS),
-                Integer.parseInt((String) dictionaryConfig.get(KEY_SSH_PORT)));
-        final InetSocketAddress tcpServerAddress = new InetSocketAddress((String) dictionaryConfig.get(KEY_TCP_ADDRESS),
-                Integer.parseInt((String) dictionaryConfig.get(KEY_TCP_PORT)));
+        final InetSocketAddress sshServerAddress = new InetSocketAddress((String) dictionaryConfig.get(SSH_ADDRESS_PROP),
+                Integer.parseInt((String) dictionaryConfig.get(SSH_PORT_PROP)));
+        final InetSocketAddress tcpServerAddress = new InetSocketAddress((String) dictionaryConfig.get(TCP_ADDRESS_PROP),
+                Integer.parseInt((String) dictionaryConfig.get(TCP_PORT_PROP)));
 
-        netconfConfiguration = new NetconfConfigurationHolder(tcpServerAddress, sshServerAddress,
-                (String) dictionaryConfig.get(KEY_SSH_PK_PATH));
+        netconfConfiguration = new NetconfConfigurationHolder(tcpServerAddress,
+                sshServerAddress,
+                (String) dictionaryConfig.get(SSH_PK_PATH_PROP));
 
-        LOG.info("Netconf configuration was updated: {}", dictionaryConfig.toString());
+        LOG.debug("CSS netconf server configuration was updated: {}", dictionaryConfig.toString());
     }
 
     public InetSocketAddress getSshServerAddress(){
index a752322d5042131834b3f06cade1ad630c7c9f98..99371503b589b53e4394d4bd71bab3f898f9e58a 100644 (file)
@@ -28,12 +28,12 @@ public class NetconfConfigurationActivator implements BundleActivator {
     @Override
     public void stop(BundleContext bundleContext) {
         if (configService != null) {
-          configService.unregister();
-          configService = null;
+            configService.unregister();
+            configService = null;
         }
     }
 
-    private Hashtable<String, String> getNetconfConfigProperties(){
+    private Hashtable<String, String> getNetconfConfigProperties() {
         Hashtable<String, String> properties = new Hashtable<>();
         properties.put(Constants.SERVICE_PID, CONFIG_PID);
         return properties;
index 74b3a089fa7aa506dc7a3bb49cce314a9f1f9a05..9eb88c4cb2b1fe088bf7ac87352ba939fb5500ca 100644 (file)
@@ -16,7 +16,9 @@ final class NetconfConfigurationHolder {
     private final InetSocketAddress sshServerAddress;
     private final String privateKeyPath;
 
-    NetconfConfigurationHolder(InetSocketAddress tcpServerAddress, InetSocketAddress sshServerAddress, String privateKeyPath){
+    NetconfConfigurationHolder(final InetSocketAddress tcpServerAddress,
+                               final InetSocketAddress sshServerAddress,
+                               final String privateKeyPath) {
         this.tcpServerAddress = tcpServerAddress;
         this.sshServerAddress = sshServerAddress;
         this.privateKeyPath = privateKeyPath;
@@ -33,5 +35,4 @@ final class NetconfConfigurationHolder {
     InetSocketAddress getTcpServerAddress() {
         return tcpServerAddress;
     }
-
 }
index 4e363b14b27200ef9a672d4b214d05175e9b0f82..72106403225d806818188ba9677ec77235794a85 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ * Copyright (c) 2016 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,
@@ -8,76 +8,73 @@
 
 package org.opendaylight.netconf.util.osgi;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 
-import com.google.common.base.Optional;
-import io.netty.channel.local.LocalAddress;
-import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.cm.ManagedService;
 
 public class NetconfConfigUtilTest {
 
-    private BundleContext bundleContext;
+    @Mock
+    private ServiceReference<ManagedService> serviceRef;
 
-    @Before
-    public void setUp() throws Exception {
-        bundleContext = mock(BundleContext.class);
-    }
-
-    @Test
-    public void testNetconfConfigUtil() throws Exception {
-        assertEquals(NetconfConfigUtil.getNetconfLocalAddress(), new LocalAddress("netconf"));
-
-        doReturn("").when(bundleContext).getProperty("netconf.connectionTimeoutMillis");
-        assertEquals(NetconfConfigUtil.extractTimeoutMillis(bundleContext), NetconfConfigUtil.DEFAULT_TIMEOUT_MILLIS);
-
-        doReturn("a").when(bundleContext).getProperty("netconf.connectionTimeoutMillis");
-        assertEquals(NetconfConfigUtil.extractTimeoutMillis(bundleContext), NetconfConfigUtil.DEFAULT_TIMEOUT_MILLIS);
-    }
+    @Mock
+    private ServiceReference<ManagedService> netconfConfigurationRef;
 
-    @Test
-    public void testgetPrivateKeyKey() throws Exception {
-        assertEquals(NetconfConfigUtil.getPrivateKeyKey(), "netconf.ssh.pk.path");
-    }
-
-    @Test
-    public void testgetNetconfServerAddressKey() throws Exception {
-        NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
-        assertEquals(NetconfConfigUtil.getNetconfServerAddressKey(prop), "netconf.tcp.address");
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
     }
 
     @Test
-    public void testExtractNetconfServerAddress() throws Exception {
-        NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
-        doReturn("").when(bundleContext).getProperty(anyString());
-        assertEquals(NetconfConfigUtil.extractNetconfServerAddress(bundleContext, prop), Optional.absent());
-    }
+    public void testGetNetconfConfigurationService() throws Exception {
+        final Collection<ServiceReference<ManagedService>> services = new ArrayList<>();
+        services.add(serviceRef);
+        services.add(netconfConfigurationRef);
+        final BundleContext context = mock(BundleContext.class);
+        doReturn(services).when(context).getServiceReferences(ManagedService.class, null);
+        final ManagedService service = mock(ManagedService.class);
+        doReturn(service).when(context).getService(serviceRef);
+        doReturn(NetconfConfiguration.getInstance()).when(context).getService(netconfConfigurationRef);
+        final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+                NetconfConfigUtil.getNetconfConfigurationService(context);
+        Assert.assertTrue(netconfConfigurationOptional.isPresent());
+        Assert.assertEquals(NetconfConfiguration.getInstance(), netconfConfigurationOptional.get());
 
-    @Test
-    public void testExtractNetconfServerAddress2() throws Exception {
-        NetconfConfigUtil.InfixProp prop = NetconfConfigUtil.InfixProp.tcp;
-        doReturn("1.1.1.1").when(bundleContext).getProperty("netconf.tcp.address");
-        doReturn("20").when(bundleContext).getProperty("netconf.tcp.port");
-        Optional<InetSocketAddress> inetSocketAddressOptional = NetconfConfigUtil.extractNetconfServerAddress(bundleContext, prop);
-        assertTrue(inetSocketAddressOptional.isPresent());
-        assertEquals(inetSocketAddressOptional.get(), new InetSocketAddress("1.1.1.1", 20));
     }
 
     @Test
-    public void testGetPrivateKeyPath() throws Exception {
-        doReturn("path").when(bundleContext).getProperty("netconf.ssh.pk.path");
-        assertEquals(NetconfConfigUtil.getPrivateKeyPath(bundleContext).get(), "path");
+    public void testGetNetconfConfigurationServiceAbsent() throws Exception {
+        final Collection<ServiceReference<ManagedService>> services = new ArrayList<>();
+        services.add(serviceRef);
+        final BundleContext context = mock(BundleContext.class);
+        doReturn(services).when(context).getServiceReferences(ManagedService.class, null);
+        final ManagedService service = mock(ManagedService.class);
+        doReturn(service).when(context).getService(serviceRef);
+        final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+                NetconfConfigUtil.getNetconfConfigurationService(context);
+        Assert.assertFalse(netconfConfigurationOptional.isPresent());
     }
 
     @Test
-    public void testGetPrivateKeyPathNotPresent() throws Exception {
-        doReturn(null).when(bundleContext).getProperty("netconf.ssh.pk.path");
-        assertEquals(NetconfConfigUtil.getPrivateKeyPath(bundleContext), Optional.absent());
+    public void testGetNetconfConfigurationServiceInvalidSyntax() throws Exception {
+        final BundleContext context = mock(BundleContext.class);
+        final InvalidSyntaxException exception = new InvalidSyntaxException("Invalid syntax", "filter");
+        doThrow(exception).when(context).getServiceReferences(ManagedService.class, null);
+        final java.util.Optional<NetconfConfiguration> netconfConfigurationOptional =
+                NetconfConfigUtil.getNetconfConfigurationService(context);
+        Assert.assertFalse(netconfConfigurationOptional.isPresent());
     }
 }
diff --git a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationActivatorTest.java b/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationActivatorTest.java
new file mode 100644 (file)
index 0000000..7ace84b
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016 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.netconf.util.osgi;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ManagedService;
+
+public class NetconfConfigurationActivatorTest {
+
+    @Mock
+    private BundleContext context;
+    @Mock
+    private ServiceRegistration registration;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doReturn(registration).when(context)
+                .registerService(eq(ManagedService.class), any(NetconfConfiguration.class), any());
+        doNothing().when(registration).unregister();
+    }
+
+    @Test
+    public void testStartStop() throws Exception {
+        final NetconfConfigurationActivator activator = new NetconfConfigurationActivator();
+        activator.start(context);
+        final Dictionary<String, String> props = new Hashtable<>();
+        props.put(Constants.SERVICE_PID, "netconf");
+        verify(context).registerService(eq(ManagedService.class), eq(NetconfConfiguration.getInstance()), eq(props));
+        activator.stop(context);
+        verify(registration).unregister();
+    }
+
+}
\ No newline at end of file
diff --git a/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationTest.java b/netconf/netconf-util/src/test/java/org/opendaylight/netconf/util/osgi/NetconfConfigurationTest.java
new file mode 100644 (file)
index 0000000..c80f388
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2016 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.netconf.util.osgi;
+
+import io.netty.channel.local.LocalAddress;
+import java.net.InetSocketAddress;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NetconfConfigurationTest {
+
+    @Test
+    public void testUpdated() throws Exception {
+        final NetconfConfiguration config = NetconfConfiguration.getInstance();
+        Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+        Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+        Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+        final Dictionary<String, String> newValues = new Hashtable<>();
+        final String newSshIp = "192.168.1.1";
+        final String newTcpIp = "192.168.1.2";
+        final int newSshPort = 1234;
+        final int newTcpPort = 4567;
+        final String newSshKeyPath = "./new_folder/configuration/RSA.pk";
+        newValues.put("ssh-address", newSshIp);
+        newValues.put("ssh-port", Integer.toString(newSshPort));
+        newValues.put("tcp-address", newTcpIp);
+        newValues.put("tcp-port", Integer.toString(newTcpPort));
+        newValues.put("ssh-pk-path", newSshKeyPath);
+        config.updated(newValues);
+        Assert.assertEquals(new InetSocketAddress(newSshIp, newSshPort), config.getSshServerAddress());
+        Assert.assertEquals(new InetSocketAddress(newTcpIp, newTcpPort), config.getTcpServerAddress());
+        Assert.assertEquals(newSshKeyPath, config.getPrivateKeyPath());
+    }
+
+    @Test
+    public void testUpdatedNull() throws Exception {
+        final NetconfConfiguration config = NetconfConfiguration.getInstance();
+        Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+        Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+        Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+        final Dictionary<String, String> nullDictionary = null;
+        config.updated(nullDictionary);
+        Assert.assertEquals(new InetSocketAddress("0.0.0.0", 1830), config.getSshServerAddress());
+        Assert.assertEquals(new InetSocketAddress("127.0.0.1", 8383), config.getTcpServerAddress());
+        Assert.assertEquals("./configuration/RSA.pk", config.getPrivateKeyPath());
+    }
+}
\ No newline at end of file
index 421e52da71978af327083e7f39b83aa3262015a7..2a56d01b1a4f4702ba4d95b7560dfb76d92c948f 100644 (file)
@@ -24,6 +24,7 @@ import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
 import org.opendaylight.netconf.sal.connect.netconf.listener.NetconfSessionPreferences;
 import org.opendaylight.netconf.sal.connect.netconf.sal.tx.ReadOnlyTx;
 import org.opendaylight.netconf.sal.connect.netconf.sal.tx.ReadWriteTx;
+import org.opendaylight.netconf.sal.connect.netconf.sal.tx.TxChain;
 import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteCandidateRunningTx;
 import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteCandidateTx;
 import org.opendaylight.netconf.sal.connect.netconf.sal.tx.WriteRunningTx;
@@ -38,8 +39,8 @@ public final class NetconfDeviceDataBroker implements DOMDataBroker {
     private final NetconfBaseOps netconfOps;
 
     private final boolean rollbackSupport;
-    private boolean candidateSupported;
-    private boolean runningWritable;
+    private final boolean candidateSupported;
+    private final boolean runningWritable;
 
     public NetconfDeviceDataBroker(final RemoteDeviceId id, final SchemaContext schemaContext, final DOMRpcService rpc, final NetconfSessionPreferences netconfSessionPreferences) {
         this.id = id;
@@ -83,7 +84,7 @@ public final class NetconfDeviceDataBroker implements DOMDataBroker {
 
     @Override
     public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
-        throw new UnsupportedOperationException(id + ": Transaction chains not supported for netconf mount point");
+        return new TxChain(this, listener);
     }
 
     @Override
index 702657dabaf244138074c2435fcd8fa9dc686636..d0f89bc9b4910520baf01725160fe1965f3b13ec 100644 (file)
@@ -16,14 +16,18 @@ import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import javax.annotation.Nullable;
 import org.opendaylight.controller.config.util.xml.DocumentedException;
 import org.opendaylight.controller.md.sal.common.api.TransactionStatus;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
 import org.opendaylight.netconf.api.NetconfDocumentedException;
 import org.opendaylight.netconf.sal.connect.netconf.util.NetconfBaseOps;
 import org.opendaylight.netconf.sal.connect.util.RemoteDeviceId;
+import org.opendaylight.yangtools.yang.common.RpcError;
 import org.opendaylight.yangtools.yang.common.RpcResult;
 import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
@@ -42,6 +46,7 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
     protected final NetconfBaseOps netOps;
     protected final boolean rollbackSupport;
     protected final List<ListenableFuture<DOMRpcResult>> resultsFutures;
+    private final List<TxListener> listeners = new CopyOnWriteArrayList<>();
     // Allow commit to be called only once
     protected boolean finished = false;
 
@@ -70,7 +75,7 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
         if(isFinished()) {
             return false;
         }
-
+        listeners.forEach(listener -> listener.onTransactionCancelled(this));
         finished = true;
         cleanup();
         return true;
@@ -131,10 +136,27 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
 
     @Override
     public final ListenableFuture<RpcResult<TransactionStatus>> commit() {
+        listeners.forEach(listener -> listener.onTransactionSubmitted(this));
         checkNotFinished();
         finished = true;
+        final ListenableFuture<RpcResult<TransactionStatus>> result = performCommit();
+        Futures.addCallback(result, new FutureCallback<RpcResult<TransactionStatus>>() {
+            @Override
+            public void onSuccess(@Nullable final RpcResult<TransactionStatus> result) {
+                if (result != null && result.isSuccessful()) {
+                    listeners.forEach(txListener -> txListener.onTransactionSuccessful(AbstractWriteTx.this));
+                } else {
+                    final TransactionCommitFailedException cause = new TransactionCommitFailedException("Transaction failed", result.getErrors().toArray(new RpcError[result.getErrors().size()]));
+                    listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, cause));
+                }
+            }
 
-        return performCommit();
+            @Override
+            public void onFailure(final Throwable t) {
+                listeners.forEach(listener -> listener.onTransactionFailed(AbstractWriteTx.this, t));
+            }
+        });
+        return result;
     }
 
     protected abstract ListenableFuture<RpcResult<TransactionStatus>> performCommit();
@@ -172,15 +194,20 @@ public abstract class AbstractWriteTx implements DOMDataWriteTransaction {
             public void onFailure(final Throwable throwable) {
                 final NetconfDocumentedException exception =
                         new NetconfDocumentedException(
-                                new DocumentedException(id + ":RPC during tx returned an exception",
-                                        new Exception(throwable),
-                                        DocumentedException.ErrorType.APPLICATION,
-                                        DocumentedException.ErrorTag.OPERATION_FAILED,
-                                        DocumentedException.ErrorSeverity.ERROR));
+                                id + ":RPC during tx returned an exception",
+                                new Exception(throwable),
+                                DocumentedException.ErrorType.APPLICATION,
+                                DocumentedException.ErrorTag.OPERATION_FAILED,
+                                DocumentedException.ErrorSeverity.ERROR);
                 transformed.setException(exception);
             }
         });
 
         return transformed;
     }
+
+    AutoCloseable addListener(final TxListener listener) {
+        listeners.add(listener);
+        return () -> listeners.remove(listener);
+    }
 }
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChain.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChain.java
new file mode 100644 (file)
index 0000000..7dbc835
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016 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.netconf.sal.connect.netconf.sal.tx;
+
+import com.google.common.base.Preconditions;
+import java.util.HashMap;
+import java.util.Map;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * {@link DOMTransactionChain} implementation for Netconf connector.
+ */
+public class TxChain implements DOMTransactionChain, TxListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(TxChain.class);
+
+    private final DOMDataBroker dataBroker;
+    private final TransactionChainListener listener;
+    /**
+     * Submitted transactions that haven't completed yet.
+     */
+    private final Map<DOMDataWriteTransaction, AutoCloseable> pendingTransactions = new HashMap<>();
+
+    /**
+     * Transaction created by this chain that hasn't been submitted or cancelled yet.
+     */
+    private AbstractWriteTx currentTransaction = null;
+    private boolean closed = false;
+    private boolean successful = true;
+
+    public TxChain(final DOMDataBroker dataBroker, final TransactionChainListener listener) {
+        this.dataBroker = dataBroker;
+        this.listener = listener;
+    }
+
+    @Override
+    public synchronized DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+        checkOperationPermitted();
+        return dataBroker.newReadOnlyTransaction();
+    }
+
+    @Override
+    public synchronized AbstractWriteTx newWriteOnlyTransaction() {
+        checkOperationPermitted();
+        final DOMDataWriteTransaction writeTransaction = dataBroker.newWriteOnlyTransaction();
+        Preconditions.checkState(writeTransaction instanceof AbstractWriteTx);
+        final AbstractWriteTx pendingWriteTx = (AbstractWriteTx) writeTransaction;
+        pendingTransactions.put(pendingWriteTx, pendingWriteTx.addListener(this));
+        currentTransaction = pendingWriteTx;
+        return pendingWriteTx;
+    }
+
+    @Override
+    public synchronized DOMDataReadWriteTransaction newReadWriteTransaction() {
+        return new ReadWriteTx(dataBroker.newReadOnlyTransaction(), newWriteOnlyTransaction());
+    }
+
+    @Override
+    public synchronized void close() {
+        if (!closed) {
+            closed = true;
+            notifyChainListenerSuccess();
+        }
+    }
+
+    @Override
+    public synchronized void onTransactionSuccessful(final AbstractWriteTx transaction) {
+        removePendingTx(transaction);
+        notifyChainListenerSuccess();
+    }
+
+    @Override
+    public synchronized void onTransactionFailed(final AbstractWriteTx transaction, final Throwable cause) {
+        removePendingTx(transaction);
+        successful = false;
+        if (currentTransaction != null) {
+            currentTransaction.cancel();
+        }
+        listener.onTransactionChainFailed(this, transaction, cause);
+    }
+
+    @Override
+    public synchronized void onTransactionSubmitted(final AbstractWriteTx transaction) {
+        currentTransaction = null;
+    }
+
+    @Override
+    public synchronized void onTransactionCancelled(final AbstractWriteTx transaction) {
+        removePendingTx(transaction);
+        currentTransaction = null;
+    }
+
+    private void removePendingTx(final AbstractWriteTx transaction) {
+        try {
+            pendingTransactions.remove(transaction).close();
+        } catch (final Exception e) {
+            LOG.error("Can't remove transaction listener registration", e);
+        }
+    }
+
+    /**
+     * Checks, if chain isn't closed and if there is no not submitted write transaction waiting.
+     */
+    private void checkOperationPermitted() {
+        if (closed) {
+            throw new TransactionChainClosedException("Transaction chain was closed");
+        }
+        Preconditions.checkState(currentTransaction == null, "Last write transaction has not finished yet");
+    }
+
+    private void notifyChainListenerSuccess() {
+        if (closed && pendingTransactions.isEmpty() && successful) {
+            listener.onTransactionChainSuccessful(this);
+        }
+    }
+
+}
diff --git a/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxListener.java b/netconf/sal-netconf-connector/src/main/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxListener.java
new file mode 100644 (file)
index 0000000..1c97cab
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2016 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.netconf.sal.connect.netconf.sal.tx;
+
+interface TxListener {
+
+    /**
+     * Invoked, when transaction completes successfully.
+     * @param transaction transaction
+     */
+    void onTransactionSuccessful(AbstractWriteTx transaction);
+
+    /**
+     * Invoked, when transaction fails.
+     *
+     * @param transaction transaction
+     * @param cause cause
+     */
+    void onTransactionFailed(AbstractWriteTx transaction, Throwable cause);
+
+    /**
+     * Invoked, when transaction is cancelled.
+     * @param transaction transaction
+     */
+    void onTransactionCancelled(AbstractWriteTx transaction);
+
+    /**
+     * Invoked, when transaction is submitted.
+     * @param transaction transaction
+     */
+    void onTransactionSubmitted(AbstractWriteTx transaction);
+
+
+}
index 3efa83719f3caa3ec787c3ee76f1303669259e28..7d4dd0abaecdecf03333cf144653ea68b62599c2 100644 (file)
@@ -15,6 +15,7 @@ import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_CANDIDATE_QNAME;
 import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTransformUtil.NETCONF_FILTER_QNAME;
@@ -24,8 +25,10 @@ import static org.opendaylight.netconf.sal.connect.netconf.util.NetconfMessageTr
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
 import java.net.InetSocketAddress;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -140,4 +143,51 @@ public class NetconfDeviceWriteOnlyTxTest {
         inOrder.verify(rpc).invokeRpc(toPath(NetconfMessageTransformUtil.NETCONF_UNLOCK_QNAME), NetconfBaseOps.getUnLockContent(NETCONF_RUNNING_QNAME));
     }
 
+    @Test
+    public void testListenerSuccess() throws Exception {
+        doReturn(Futures.immediateCheckedFuture(new DefaultDOMRpcResult((NormalizedNode<?, ?>) null)))
+                .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+        final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+        final TxListener listener = mock(TxListener.class);
+        tx.addListener(listener);
+        tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+        tx.submit();
+        verify(listener).onTransactionSubmitted(tx);
+        verify(listener).onTransactionSuccessful(tx);
+        verify(listener, never()).onTransactionFailed(eq(tx), any());
+        verify(listener, never()).onTransactionCancelled(tx);
+    }
+
+    @Test
+    public void testListenerCancellation() throws Exception {
+        doReturn(Futures.immediateCheckedFuture(new DefaultDOMRpcResult((NormalizedNode<?, ?>) null)))
+                .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+        final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+        final TxListener listener = mock(TxListener.class);
+        tx.addListener(listener);
+        tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+        tx.cancel();
+        verify(listener).onTransactionCancelled(tx);
+        verify(listener, never()).onTransactionSubmitted(tx);
+        verify(listener, never()).onTransactionSuccessful(tx);
+        verify(listener, never()).onTransactionFailed(eq(tx), any());
+    }
+
+    @Test
+    public void testListenerFailure() throws Exception {
+        final IllegalStateException cause = new IllegalStateException("Failed tx");
+        doReturn(Futures.immediateFailedCheckedFuture(cause))
+                .when(rpc).invokeRpc(any(SchemaPath.class), any(NormalizedNode.class));
+        final WriteCandidateTx tx = new WriteCandidateTx(id, new NetconfBaseOps(rpc, BaseSchema.BASE_NETCONF_CTX.getSchemaContext()), false);
+        final TxListener listener = mock(TxListener.class);
+        tx.addListener(listener);
+        tx.delete(LogicalDatastoreType.CONFIGURATION, yangIId);
+        tx.submit();
+        final ArgumentCaptor<Exception> excCaptor = ArgumentCaptor.forClass(Exception.class);
+        verify(listener).onTransactionSubmitted(tx);
+        verify(listener).onTransactionFailed(eq(tx), excCaptor.capture());
+        Assert.assertEquals(cause, excCaptor.getValue().getCause().getCause());
+        verify(listener, never()).onTransactionSuccessful(tx);
+        verify(listener, never()).onTransactionCancelled(tx);
+    }
 }
diff --git a/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChainTest.java b/netconf/sal-netconf-connector/src/test/java/org/opendaylight/netconf/sal/connect/netconf/sal/tx/TxChainTest.java
new file mode 100644 (file)
index 0000000..e56b02d
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2016 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.netconf.sal.connect.netconf.sal.tx;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainClosedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+
+public class TxChainTest {
+
+    @Mock
+    private DOMDataBroker broker;
+    @Mock
+    private TransactionChainListener listener;
+    @Mock
+    private DOMDataReadOnlyTransaction readOnlyTx;
+    @Mock
+    private AbstractWriteTx writeOnlyTx1;
+    @Mock
+    private AbstractWriteTx writeOnlyTx2;
+    @Mock
+    private AbstractWriteTx writeOnlyTx3;
+    @Mock
+    private AutoCloseable registration1;
+    @Mock
+    private AutoCloseable registration2;
+    @Mock
+    private AutoCloseable registration3;
+    private final ArgumentCaptor<TxListener> captor = ArgumentCaptor.forClass(TxListener.class);
+    private TxChain chain;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(broker.newReadOnlyTransaction()).thenReturn(readOnlyTx);
+        when(broker.newWriteOnlyTransaction()).thenReturn(writeOnlyTx1).thenReturn(writeOnlyTx2).thenReturn(writeOnlyTx3);
+        when(writeOnlyTx1.addListener(any())).thenReturn(registration1);
+        when(writeOnlyTx2.addListener(any())).thenReturn(registration2);
+        when(writeOnlyTx3.addListener(any())).thenReturn(registration3);
+        chain = new TxChain(broker, listener);
+    }
+
+    @Test()
+    public void testNewReadOnlyTransactionPrevSubmitted() throws Exception {
+        chain.newWriteOnlyTransaction();
+        verify(writeOnlyTx1).addListener(captor.capture());
+        captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+        chain.newReadOnlyTransaction();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNewReadOnlyTransactionPrevNotSubmitted() throws Exception {
+        chain.newWriteOnlyTransaction();
+        chain.newReadOnlyTransaction();
+    }
+
+    @Test
+    public void testNewReadWriteTransactionPrevSubmitted() throws Exception {
+        chain.newReadWriteTransaction();
+        verify(writeOnlyTx1).addListener(captor.capture());
+        captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+        chain.newReadWriteTransaction();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNewReadWriteTransactionPrevNotSubmitted() throws Exception {
+        chain.newReadWriteTransaction();
+        chain.newReadWriteTransaction();
+    }
+
+    @Test
+    public void testNewWriteOnlyTransactionPrevSubmitted() throws Exception {
+        chain.newWriteOnlyTransaction();
+        verify(writeOnlyTx1).addListener(captor.capture());
+        captor.getValue().onTransactionSubmitted(writeOnlyTx1);
+        chain.newWriteOnlyTransaction();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testNewWriteOnlyTransactionPrevNotSubmitted() throws Exception {
+        chain.newWriteOnlyTransaction();
+        chain.newWriteOnlyTransaction();
+    }
+
+    @Test(expected = TransactionChainClosedException.class)
+    public void testCloseAfterFinished() throws Exception {
+        chain.close();
+        verify(listener).onTransactionChainSuccessful(chain);
+        chain.newReadOnlyTransaction();
+    }
+
+    @Test
+    public void testChainFail() throws Exception {
+        final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeOnlyTx1).addListener(captor.capture());
+        writeTx.submit();
+        final TransactionCommitFailedException cause = new TransactionCommitFailedException("fail");
+        captor.getValue().onTransactionFailed(writeOnlyTx1, cause);
+        verify(registration1).close();
+        verify(listener).onTransactionChainFailed(chain, writeOnlyTx1, cause);
+    }
+
+    @Test
+    public void testChainSuccess() throws Exception {
+        final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+        chain.close();
+        verify(writeOnlyTx1).addListener(captor.capture());
+        writeTx.submit();
+        captor.getValue().onTransactionSuccessful(writeOnlyTx1);
+        verify(registration1).close();
+        verify(listener).onTransactionChainSuccessful(chain);
+    }
+
+    @Test
+    public void testCancel() throws Exception {
+        final AbstractWriteTx writeTx = chain.newWriteOnlyTransaction();
+        verify(writeOnlyTx1).addListener(captor.capture());
+        writeTx.cancel();
+        captor.getValue().onTransactionCancelled(writeOnlyTx1);
+        chain.newWriteOnlyTransaction();
+    }
+
+    @Test
+    public void testMultiplePendingTransactions() throws Exception {
+        //create 1st tx
+        final AbstractWriteTx writeTx1 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor1 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeOnlyTx1).addListener(captor1.capture());
+        //submit 1st tx
+        writeTx1.submit();
+        captor1.getValue().onTransactionSubmitted(writeOnlyTx1);
+
+        //create 2nd tx
+        final AbstractWriteTx writeTx2 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor2 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeTx2).addListener(captor2.capture());
+        //submit 2nd tx
+        writeTx2.submit();
+        captor2.getValue().onTransactionSubmitted(writeOnlyTx2);
+
+        //create 3rd tx
+        final AbstractWriteTx writeTx3 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor3 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeTx3).addListener(captor3.capture());
+        //cancel 3rd tx
+        writeTx3.cancel();
+        captor3.getValue().onTransactionCancelled(writeOnlyTx3);
+
+        //close chain
+        chain.close();
+
+        //complete first two transactions successfully
+        captor1.getValue().onTransactionSuccessful(writeOnlyTx1);
+        captor2.getValue().onTransactionSuccessful(writeOnlyTx2);
+
+        verify(registration1).close();
+        verify(registration2).close();
+        verify(registration3).close();
+        verify(listener).onTransactionChainSuccessful(chain);
+    }
+
+    @Test
+    public void testMultiplePendingTransactionsFail() throws Exception {
+        //create 1st tx
+        final AbstractWriteTx writeTx1 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor1 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeOnlyTx1).addListener(captor1.capture());
+        //submit 1st tx
+        writeTx1.submit();
+        captor1.getValue().onTransactionSubmitted(writeOnlyTx1);
+
+        //create 2nd tx
+        final AbstractWriteTx writeTx2 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor2 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeTx2).addListener(captor2.capture());
+        //submit 2nd tx
+        writeTx2.submit();
+        captor2.getValue().onTransactionSubmitted(writeOnlyTx2);
+
+        //create 3rd tx
+        final AbstractWriteTx writeTx3 = chain.newWriteOnlyTransaction();
+        final ArgumentCaptor<TxListener> captor3 = ArgumentCaptor.forClass(TxListener.class);
+        verify(writeTx3).addListener(captor3.capture());
+
+        chain.close();
+
+        //fail 1st transaction
+        final Exception cause1 = new Exception("fail");
+        captor1.getValue().onTransactionFailed(writeOnlyTx1, cause1);
+        //current unsubmitted transaction should be cancelled
+        verify(writeTx3).cancel();
+        captor3.getValue().onTransactionCancelled(writeTx3);
+        //2nd transaction success
+        captor2.getValue().onTransactionSuccessful(writeOnlyTx2);
+
+        verify(registration1).close();
+        verify(registration2).close();
+        verify(registration3).close();
+        verify(listener).onTransactionChainFailed(chain, writeOnlyTx1, cause1);
+        // 1 transaction failed, onTransactionChainSuccessful must not be called
+        verify(listener, never()).onTransactionChainSuccessful(chain);
+    }
+}
\ No newline at end of file
index 165decc6d4d6a8a41a05722026753ee375a696ea..3ade59f8a5822b4aa209ec2cc8c0ea8d1f5192c3 100644 (file)
@@ -23,7 +23,6 @@ import io.netty.channel.local.LocalAddress;
 import io.netty.channel.nio.NioEventLoopGroup;
 import io.netty.util.HashedWheelTimer;
 import java.io.Closeable;
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.BindException;
@@ -56,6 +55,7 @@ import org.opendaylight.netconf.monitoring.osgi.NetconfMonitoringOperationServic
 import org.opendaylight.netconf.ssh.SshProxyServer;
 import org.opendaylight.netconf.ssh.SshProxyServerConfiguration;
 import org.opendaylight.netconf.ssh.SshProxyServerConfigurationBuilder;
+import org.opendaylight.netconf.test.tool.customrpc.SettableOperationProvider;
 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -102,9 +102,9 @@ public class NetconfDeviceSimulator implements Closeable {
         this.nioExecutor = nioExecutor;
     }
 
-    private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities, final boolean exi, final int generateConfigsTimeout,
-                                                         final Optional<File> notificationsFile, final boolean mdSal, final Optional<File> initialConfigXMLFile,
-                                                         final SchemaSourceProvider<YangTextSchemaSource> sourceProvider) {
+    private NetconfServerDispatcherImpl createDispatcher(final Set<Capability> capabilities,
+                                                         final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+                                                         final TesttoolParameters params) {
 
         final Set<Capability> transformedCapabilities = Sets.newHashSet(Collections2.transform(capabilities, new Function<Capability, Capability>() {
             @Override
@@ -117,49 +117,66 @@ public class NetconfDeviceSimulator implements Closeable {
                 }
             }
         }));
-
-        final SessionIdProvider idProvider = new SessionIdProvider();
-
-        final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
-        final NetconfOperationServiceFactory operationProvider = mdSal ? new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider) :
-                new SimulatedOperationProvider(idProvider, transformedCapabilities, notificationsFile, initialConfigXMLFile);
-
         transformedCapabilities.add(new BasicCapability("urn:ietf:params:netconf:capability:candidate:1.0"));
-
         final NetconfMonitoringService monitoringService1 = new DummyMonitoringService(transformedCapabilities);
+        final SessionIdProvider idProvider = new SessionIdProvider();
 
-        final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
-                new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
-                        new NetconfMonitoringOperationService(monitoringService1));
-        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
-        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+        final NetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = createOperationServiceFactory(sourceProvider, params, transformedCapabilities, monitoringService1, idProvider);
 
-        final Set<String> serverCapabilities = exi
+        final Set<String> serverCapabilities = params.exi
                 ? NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES
                 : Sets.newHashSet(XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0, XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1);
 
         final NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new TesttoolNegotiationFactory(
-                hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, generateConfigsTimeout, monitoringService1, serverCapabilities);
+                hashedWheelTimer, aggregatedNetconfOperationServiceFactory, idProvider, params.generateConfigsTimeout, monitoringService1, serverCapabilities);
 
         final NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(
                 serverNegotiatorFactory);
         return new NetconfServerDispatcherImpl(serverChannelInitializer, nettyThreadgroup, nettyThreadgroup);
     }
 
+    private NetconfOperationServiceFactory createOperationServiceFactory(final SchemaSourceProvider<YangTextSchemaSource> sourceProvider,
+                                                                         final TesttoolParameters params,
+                                                                         final Set<Capability> transformedCapabilities,
+                                                                         final NetconfMonitoringService monitoringService1,
+                                                                         final SessionIdProvider idProvider) {
+        final AggregatedNetconfOperationServiceFactory aggregatedNetconfOperationServiceFactory = new AggregatedNetconfOperationServiceFactory();
+
+        final NetconfOperationServiceFactory operationProvider;
+        if (params.mdSal) {
+            operationProvider = new MdsalOperationProvider(idProvider, transformedCapabilities, schemaContext, sourceProvider);
+        } else {
+            operationProvider = new SimulatedOperationProvider(idProvider, transformedCapabilities,
+                    Optional.fromNullable(params.notificationFile),
+                    Optional.fromNullable(params.initialConfigXMLFile));
+        }
+
+
+        final NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory monitoringService =
+                new NetconfMonitoringActivator.NetconfMonitoringOperationServiceFactory(
+                        new NetconfMonitoringOperationService(monitoringService1));
+        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(operationProvider);
+        aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(monitoringService);
+        if (params.rpcConfig != null) {
+            final SettableOperationProvider settableService = new SettableOperationProvider(params.rpcConfig);
+            aggregatedNetconfOperationServiceFactory.onAddNetconfOperationServiceFactory(settableService);
+        }
+        return aggregatedNetconfOperationServiceFactory;
+    }
+
     public List<Integer> start(final TesttoolParameters params) {
         LOG.info("Starting {}, {} simulated devices starting on port {}", params.deviceCount, params.ssh ? "SSH" : "TCP", params.startingPort);
 
         final SharedSchemaRepository schemaRepo = new SharedSchemaRepository("netconf-simulator");
         final Set<Capability> capabilities = parseSchemasToModuleCapabilities(params, schemaRepo);
 
-        final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities, params.exi, params.generateConfigsTimeout,
-                Optional.fromNullable(params.notificationFile), params.mdSal, Optional.fromNullable(params.initialConfigXMLFile),
+        final NetconfServerDispatcherImpl dispatcher = createDispatcher(capabilities,
                 new SchemaSourceProvider<YangTextSchemaSource>() {
                     @Override
                     public CheckedFuture<? extends YangTextSchemaSource, SchemaSourceException> getSource(final SourceIdentifier sourceIdentifier) {
                         return schemaRepo.getSchemaSource(sourceIdentifier, YangTextSchemaSource.class);
                     }
-                });
+                }, params);
 
         int currentPort = params.startingPort;
 
index 8218d73a15acc669599b0d03790b978ee9060076..f08daee52008fcd3e8ba6e0d8113cbffd8f4fef1 100644 (file)
@@ -94,6 +94,8 @@ public class TesttoolParameters {
 
     @Arg(dest = "thread-pool-size")
     public int threadPoolSize;
+    @Arg(dest = "rpc-config")
+    public File rpcConfig;
 
     static ArgumentParser getParser() {
         final ArgumentParser parser = ArgumentParsers.newArgumentParser("netconf testtool");
@@ -234,6 +236,11 @@ public class TesttoolParameters {
                 .setDefault(8)
                 .help("The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.")
                 .dest("thread-pool-size");
+        parser.addArgument("--rpc-config")
+                .type(File.class)
+                .help("Rpc config file. It can be used to define custom rpc behavior, or override the default one." +
+                        "Usable for testing buggy device behavior.")
+                .dest("rpc-config");
 
         return parser;
     }
@@ -323,6 +330,11 @@ public class TesttoolParameters {
                 }
             }
         }
+        if (rpcConfig != null) {
+            checkArgument(rpcConfig.exists(), "Rpc config file has to exist");
+            checkArgument(!rpcConfig.isDirectory(), "Rpc config file can't be a directory");
+            checkArgument(rpcConfig.canRead(), "Rpc config file to be readable");
+        }
     }
 
     public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpc.java
new file mode 100644 (file)
index 0000000..c232aa9
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+
+class Rpc {
+
+    @XmlElement(name = "input")
+    private XmlData input;
+
+    @XmlElement(name = "output")
+    private List<XmlData> output;
+
+    XmlData getInput() {
+        return input;
+    }
+
+    List<XmlData> getOutput() {
+        return output;
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/RpcMapping.java
new file mode 100644 (file)
index 0000000..5d3f7eb
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
+import javax.xml.bind.JAXB;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+
+/**
+ * Mapping between RPCs and responses.
+ */
+class RpcMapping {
+
+    private final Multimap<Request, Document> requestToResponseMap = ArrayListMultimap.create();
+
+    /**
+     * Creates new mapping from file.
+     *
+     * @param config config file
+     */
+    RpcMapping(final File config) {
+        final Rpcs rpcs = JAXB.unmarshal(config, Rpcs.class);
+        for (final Rpc rpc : rpcs.getRpcs()) {
+            final Stream<Document> stream = rpc.getOutput().stream()
+                    .map(XmlData::getData);
+            final XmlElement element = XmlElement.fromDomDocument(rpc.getInput().getData());
+            requestToResponseMap.putAll(new Request(element), stream::iterator);
+        }
+    }
+
+    /**
+     * Returns response matching given input. If multiple responses are configured for the same
+     * request, every invocation of this method returns next response as they are defined in the config file.
+     * Last configured response is used, when number of invocations is higher than number of configured responses
+     * for rpc.
+     *
+     * @param input request
+     * @return response document, or absent if mapping is not defined
+     */
+    Optional<Document> getResponse(final XmlElement input) {
+        final Collection<Document> responses = requestToResponseMap.get(new Request(input));
+        final Iterator<Document> iterator = responses.iterator();
+        if (iterator.hasNext()) {
+            final Document response = iterator.next();
+            if (iterator.hasNext()) {
+                iterator.remove();
+            }
+            return Optional.of(response);
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Rpc input wrapper. Needed because of custom {@link Request#equals(Object)}
+     * and {@link Request#hashCode()} implementations.
+     */
+    private static class Request {
+        private final XmlElement xmlElement;
+        private final int hashCode;
+
+        private Request(final XmlElement element) {
+            this.xmlElement = element;
+            hashCode = XmlUtil.toString(element)
+                    .replaceAll("message-id=.*(>| )", "") //message id is variable, remove it
+                    .replaceAll("\\s+", "") //remove whitespaces
+                    .hashCode();
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final Request request = (Request) o;
+            return documentEquals(this.xmlElement, request.xmlElement);
+        }
+
+        @Override
+        public int hashCode() {
+            return hashCode;
+        }
+
+        private static boolean documentEquals(final XmlElement e1, final XmlElement e2) {
+            if (!e1.getNamespaceOptionally().equals(e2.getNamespaceOptionally())) {
+                return false;
+            }
+            if (!e1.getName().equals(e2.getName())) {
+                return false;
+            }
+            if (attributesNotEquals(e1, e2)) {
+                return false;
+            }
+
+            final List<XmlElement> e1Children = e1.getChildElements();
+            final List<XmlElement> e2Children = e2.getChildElements();
+            if (e1Children.size() != e2Children.size()) {
+                return false;
+            }
+            final Iterator<XmlElement> e1Iterator = e1Children.iterator();
+            final Iterator<XmlElement> e2Iterator = e2Children.iterator();
+            while (e1Iterator.hasNext() && e2Iterator.hasNext()) {
+                if (!documentEquals(e1Iterator.next(), e2Iterator.next())) {
+                    return false;
+                }
+            }
+
+            if (e1Children.isEmpty() && e1Children.isEmpty()) {
+                try {
+                    return e1.getTextContent().equals(e2.getTextContent());
+                } catch (final DocumentedException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private static boolean attributesNotEquals(final XmlElement e1, final XmlElement e2) {
+            final Map<String, Attr> e1Attrs = e1.getAttributes();
+            final Map<String, Attr> e2Attrs = e2.getAttributes();
+            final Iterator<Map.Entry<String, Attr>> e1AttrIt = e1Attrs.entrySet().iterator();
+            final Iterator<Map.Entry<String, Attr>> e2AttrIt = e2Attrs.entrySet().iterator();
+            while (e1AttrIt.hasNext() && e2AttrIt.hasNext()) {
+                final Map.Entry<String, Attr> e1Next = e1AttrIt.next();
+                final Map.Entry<String, Attr> e2Next = e2AttrIt.next();
+                if (e1Next.getKey().equals("message-id") && e2Next.getKey().equals("message-id")) {
+                    continue;
+                }
+                if (e1Next.getKey().equals("xmlns") && e2Next.getKey().equals("xmlns")) {
+                    continue;
+                }
+                if (!e1Next.getKey().equals(e2Next.getKey())) {
+                    return true;
+                }
+                if (!e1Next.getValue().getValue().equals(e2Next.getValue().getValue())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/Rpcs.java
new file mode 100644 (file)
index 0000000..9a2ad36
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "rpcs")
+@XmlAccessorType(XmlAccessType.FIELD)
+class Rpcs {
+
+    @XmlElement(name = "rpc")
+    private List<Rpc> rpcs;
+
+    List<Rpc> getRpcs() {
+        return rpcs;
+    }
+
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableOperationProvider.java
new file mode 100644 (file)
index 0000000..d811fe7
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.controller.config.util.capability.Capability;
+import org.opendaylight.netconf.api.monitoring.CapabilityListener;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationService;
+import org.opendaylight.netconf.mapping.api.NetconfOperationServiceFactory;
+
+public class SettableOperationProvider implements NetconfOperationServiceFactory {
+
+    private final File rpcConfig;
+
+    public SettableOperationProvider(final File rpcConfig) {
+        this.rpcConfig = rpcConfig;
+    }
+
+    @Override
+    public Set<Capability> getCapabilities() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public AutoCloseable registerCapabilityListener(final CapabilityListener listener) {
+        return () -> {
+            //no op
+        };
+    }
+
+    @Override
+    public NetconfOperationService createService(final String netconfSessionIdForReporting) {
+        return new SettableOperationService(rpcConfig);
+    }
+
+    private static class SettableOperationService implements NetconfOperationService {
+
+        private final SettableRpc rpc;
+
+        private SettableOperationService(final File rpcConfig) {
+            this.rpc = new SettableRpc(rpcConfig);
+        }
+
+        @Override
+        public Set<NetconfOperation> getNetconfOperations() {
+            return Collections.singleton(rpc);
+        }
+
+        @Override
+        public void close() {
+            // no op
+        }
+    }
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/SettableRpc.java
new file mode 100644 (file)
index 0000000..fddc0be
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import java.io.File;
+import java.util.Optional;
+import org.opendaylight.controller.config.util.xml.DocumentedException;
+import org.opendaylight.controller.config.util.xml.XmlElement;
+import org.opendaylight.controller.config.util.xml.XmlUtil;
+import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
+import org.opendaylight.netconf.mapping.api.HandlingPriority;
+import org.opendaylight.netconf.mapping.api.NetconfOperation;
+import org.opendaylight.netconf.mapping.api.NetconfOperationChainedExecution;
+import org.w3c.dom.Document;
+
+/**
+ * {@link NetconfOperation} implementation. It can be configured to intercept rpcs with defined input
+ * and reply with defined output. If input isn't defined, rpc handling is delegated to the subsequent
+ * {@link NetconfOperation} which is able to handle it.
+ */
+class SettableRpc implements NetconfOperation {
+
+    private final RpcMapping mapping;
+
+    SettableRpc(final File rpcConfig) {
+        mapping = new RpcMapping(rpcConfig);
+    }
+
+    @Override
+    public HandlingPriority canHandle(final Document message) throws DocumentedException {
+        return HandlingPriority.HANDLE_WITH_DEFAULT_PRIORITY.increasePriority(1000);
+    }
+
+    @Override
+    public Document handle(final Document requestMessage, final NetconfOperationChainedExecution subsequentOperation)
+            throws DocumentedException {
+        final XmlElement requestElement = XmlElement.fromDomDocument(requestMessage);
+        final XmlElement rpcElement = requestElement.getOnlyChildElement();
+        final String msgId = requestElement.getAttribute(XmlNetconfConstants.MESSAGE_ID);
+        final Optional<Document> response = mapping.getResponse(rpcElement);
+        if (response.isPresent()) {
+            final Document document = response.get();
+            checkForError(document);
+            document.getDocumentElement().setAttribute(XmlNetconfConstants.MESSAGE_ID, msgId);
+            return document;
+        } else if (subsequentOperation.isExecutionTermination()) {
+            throw new DocumentedException("Mapping not found " + XmlUtil.toString(requestMessage),
+                    DocumentedException.ErrorType.APPLICATION, DocumentedException.ErrorTag.OPERATION_NOT_SUPPORTED,
+                    DocumentedException.ErrorSeverity.ERROR);
+        } else {
+            return subsequentOperation.execute(requestMessage);
+        }
+    }
+
+    private void checkForError(final Document document) throws DocumentedException {
+        final XmlElement rpcReply = XmlElement.fromDomDocument(document);
+        if (rpcReply.getOnlyChildElementOptionally("rpc-error").isPresent()) {
+            throw DocumentedException.fromXMLDocument(document);
+        }
+    }
+
+}
diff --git a/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java b/netconf/tools/netconf-testtool/src/main/java/org/opendaylight/netconf/test/tool/customrpc/XmlData.java
new file mode 100644 (file)
index 0000000..69fb660
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2016 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.netconf.test.tool.customrpc;
+
+import javax.xml.bind.annotation.XmlAnyElement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+class XmlData {
+    @XmlAnyElement
+    private Element data;
+
+    Document getData() {
+        return data.getOwnerDocument();
+    }
+
+}
index b323cc53802c1c49ae1868ad90bfec135de684ed..838b84a9c7d9b352c1e168b33455a939af4c3e4e 100644 (file)
   </dependencyManagement>
 
   <dependencies>
+    <dependency>
+      <groupId>com.github.mifmif</groupId>
+      <artifactId>generex</artifactId>
+      <version>1.0.0</version>
+    </dependency>
     <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-annotations</artifactId>
       <groupId>org.opendaylight.yangtools</groupId>
       <artifactId>yang-data-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>dk.brics.automaton</groupId>
+      <artifactId>automaton</artifactId>
+      <version>1.11-8</version>
+    </dependency>
   </dependencies>
 
   <build>
 
           <instructions>
             <Bundle-Name>MD SAL Rest Api Doc Generator</Bundle-Name>
+            <Embed-Dependency>generex, automaton</Embed-Dependency>
             <Import-Package>!org.apache.maven.plugin.logging,
               !org.apache.maven.project,
               !org.opendaylight.yangtools.yang2sources.spi,
       </plugin>
     </plugins>
   </build>
-</project>
+</project>
\ No newline at end of file
index f78d61951eeea0d94d3f4a4103e2566016c43c16..1f4b3b52c0e7c66a52d2f1723e5f2e1e2afa316b 100644 (file)
@@ -43,6 +43,10 @@ public class ApiDocGenerator extends BaseYangSwaggerGenerator {
         return INSTANCE;
     }
 
+    public void setDraft(final boolean newDraft) {
+        super.setDraft(newDraft);
+    }
+
     public void setSchemaService(final SchemaService schemaService) {
         this.schemaService = schemaService;
     }
index 026296babad063363a3fac6c4215ede6468b6a2d..db884580081f64fb2e524928ee81ec3403be9564 100644 (file)
@@ -47,6 +47,11 @@ public class ApiDocServiceImpl implements ApiDocService {
     @Override
     public synchronized Response getRootDoc(final UriInfo uriInfo) {
         final ApiDocGenerator generator = ApiDocGenerator.getInstance();
+        if (isNew(uriInfo)) {
+            generator.setDraft(true);
+        } else {
+            generator.setDraft(false);
+        }
         final ResourceList rootDoc = generator.getResourceListing(uriInfo);
 
         return Response.ok(rootDoc).build();
@@ -58,7 +63,11 @@ public class ApiDocServiceImpl implements ApiDocService {
     @Override
     public synchronized Response getDocByModule(final String module, final String revision, final UriInfo uriInfo) {
         final ApiDocGenerator generator = ApiDocGenerator.getInstance();
-
+        if (isNew(uriInfo)) {
+            generator.setDraft(true);
+        } else {
+            generator.setDraft(false);
+        }
         final ApiDeclaration doc = generator.getApiDeclaration(module, revision, uriInfo);
         return Response.ok(doc).build();
     }
@@ -95,17 +104,32 @@ public class ApiDocServiceImpl implements ApiDocService {
 
     @Override
     public synchronized Response getMountRootDoc(final String instanceNum, final UriInfo uriInfo) {
-        final ResourceList resourceList = MountPointSwagger.getInstance().getResourceList(uriInfo,
-                Long.parseLong(instanceNum));
+        final ResourceList resourceList;
+        if (isNew(uriInfo)) {
+            resourceList = MountPointSwagger.getInstanceDraft17().getResourceList(uriInfo,
+                    Long.parseLong(instanceNum));
+        } else {
+            resourceList = MountPointSwagger.getInstance().getResourceList(uriInfo,
+                    Long.parseLong(instanceNum));
+        }
         return Response.ok(resourceList).build();
     }
 
     @Override
     public synchronized Response getMountDocByModule(final String instanceNum, final String module,
                                                      final String revision, final UriInfo uriInfo) {
-        final ApiDeclaration api = MountPointSwagger.getInstance().getMountPointApi(uriInfo,
-                Long.parseLong(instanceNum), module, revision);
+        final ApiDeclaration api;
+        if (isNew(uriInfo)) {
+            api = MountPointSwagger.getInstanceDraft17().getMountPointApi(uriInfo,
+                    Long.parseLong(instanceNum), module, revision);
+        } else {
+            api = MountPointSwagger.getInstance().getMountPointApi(uriInfo,
+                    Long.parseLong(instanceNum), module, revision);
+        }
         return Response.ok(api).build();
     }
 
+    private static boolean isNew(final UriInfo uriInfo) {
+        return uriInfo.getBaseUri().toString().contains("/17/");
+    }
 }
index abf560ea78fe9d44cffa322a12b62f456e295f09..453a9d2da4cd066983c277efbce89d1cb18f34f9 100644 (file)
@@ -62,6 +62,7 @@ public class BaseYangSwaggerGenerator {
     protected static final String API_VERSION = "1.0.0";
     protected static final String SWAGGER_VERSION = "1.2";
     protected static final String RESTCONF_CONTEXT_ROOT = "restconf";
+    private static final String RESTCONF_DRAFT = "17";
 
     static final String MODULE_NAME_SUFFIX = "_module";
     protected static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
@@ -69,6 +70,7 @@ public class BaseYangSwaggerGenerator {
 
     // private Map<String, ApiDeclaration> MODULE_DOC_CACHE = new HashMap<>()
     private final ObjectMapper mapper = new ObjectMapper();
+    private static boolean newDraft;
 
     protected BaseYangSwaggerGenerator() {
         mapper.registerModule(new JsonOrgModule());
@@ -189,7 +191,7 @@ public class BaseYangSwaggerGenerator {
                  * are added for this node.
                  */
                 if (node.isConfiguration()) { // This node's config statement is true.
-                    resourcePath = getDataStorePath("/config/", context);
+                    resourcePath = getDataStorePath("config", context);
 
                     /*
                      * When there are two or more top container or list nodes whose config statement is true in module,
@@ -197,22 +199,25 @@ public class BaseYangSwaggerGenerator {
                      */
                     if (!hasAddRootPostLink) {
                         LOG.debug("Has added root post link for module {}", m.getName());
-                        addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, apis);
+                        addRootPostLink(m, (DataNodeContainer) node, pathParams, resourcePath, "config", apis);
+
                         hasAddRootPostLink = true;
                     }
 
-                    addApis(node, apis, resourcePath, pathParams, schemaContext, true, m.getName());
+                    addApis(node, apis, resourcePath, pathParams, schemaContext, true, m.getName(), "config");
                 }
-
                 pathParams = new ArrayList<>();
-                resourcePath = getDataStorePath("/operational/", context);
-                addApis(node, apis, resourcePath, pathParams, schemaContext, false, m.getName());
+                resourcePath = getDataStorePath("operational", context);
+
+                addApis(node, apis, resourcePath, pathParams, schemaContext, false, m.getName(), "operational");
             }
         }
 
         final Set<RpcDefinition> rpcs = m.getRpcs();
         for (final RpcDefinition rpcDefinition : rpcs) {
-            final String resourcePath = getDataStorePath("/operations/", context);
+            final String resourcePath;
+            resourcePath = getDataStorePath("operations", context);
+
             addRpcs(rpcDefinition, apis, resourcePath, schemaContext);
         }
 
@@ -238,10 +243,10 @@ public class BaseYangSwaggerGenerator {
     }
 
     private void addRootPostLink(final Module module, final DataNodeContainer node, final List<Parameter> pathParams,
-                                 final String resourcePath, final List<Api> apis) {
+                                 final String resourcePath, final String dataStore, final List<Api> apis) {
         if (containsListOrContainer(module.getChildNodes())) {
             final Api apiForRootPostUri = new Api();
-            apiForRootPostUri.setPath(resourcePath);
+            apiForRootPostUri.setPath(resourcePath.concat(getContent(dataStore)));
             apiForRootPostUri.setOperations(operationPost(module.getName() + MODULE_NAME_SUFFIX,
                     module.getDescription(), module, pathParams, true, ""));
             apis.add(apiForRootPostUri);
@@ -258,22 +263,29 @@ public class BaseYangSwaggerGenerator {
     }
 
     protected String getDataStorePath(final String dataStore, final String context) {
-        return dataStore + context;
+        if (newDraft) {
+            if ("config".contains(dataStore) || "operational".contains(dataStore)) {
+                return "/" + RESTCONF_DRAFT + "/data" + context;
+            } else {
+                return "/" + RESTCONF_DRAFT + "/operations" + context;
+            }
+        } else {
+            return "/" + dataStore + context;
+        }
     }
 
     private String generateCacheKey(final String module, final String revision) {
         return module + "(" + revision + ")";
     }
 
-    private void addApis(final DataSchemaNode node, final List<Api> apis, final String parentPath, final List<Parameter> parentPathParams, final SchemaContext schemaContext,
-                         final boolean addConfigApi, final String parentName) {
-
+    private void addApis(final DataSchemaNode node, final List<Api> apis, final String parentPath, final List<Parameter> parentPathParams,
+                         final SchemaContext schemaContext, final boolean addConfigApi, final String parentName, final String dataStore) {
         final Api api = new Api();
         final List<Parameter> pathParams = new ArrayList<>(parentPathParams);
 
-        final String resourcePath = parentPath + createPath(node, pathParams, schemaContext) + "/";
+        final String resourcePath = parentPath + "/" + createPath(node, pathParams, schemaContext);
         LOG.debug("Adding path: [{}]", resourcePath);
-        api.setPath(resourcePath);
+        api.setPath(resourcePath.concat(getContent(dataStore)));
 
         Iterable<DataSchemaNode> childSchemaNodes = Collections.<DataSchemaNode>emptySet();
         if ((node instanceof ListSchemaNode) || (node instanceof ContainerSchemaNode)) {
@@ -288,12 +300,25 @@ public class BaseYangSwaggerGenerator {
                 // keep config and operation attributes separate.
                 if (childNode.isConfiguration() == addConfigApi) {
                     final String newParent = parentName + "/" + node.getQName().getLocalName();
-                    addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi, newParent);
+                    addApis(childNode, apis, resourcePath, pathParams, schemaContext, addConfigApi, newParent, dataStore);
                 }
             }
         }
     }
 
+    protected static String getContent(final String dataStore) {
+        if (newDraft) {
+            if ("operational".contains(dataStore)) {
+                return "?content=nonconfig";
+            } else if ("config".contains(dataStore)) {
+                return "?content=config";
+            } else {
+                return "";
+            }
+        } else {
+            return "";
+        }
+    }
     private boolean containsListOrContainer(final Iterable<DataSchemaNode> nodes) {
         for (final DataSchemaNode child : nodes) {
             if (child instanceof ListSchemaNode || child instanceof ContainerSchemaNode) {
@@ -343,12 +368,21 @@ public class BaseYangSwaggerGenerator {
 
         if ((schemaNode instanceof ListSchemaNode)) {
             final List<QName> listKeys = ((ListSchemaNode) schemaNode).getKeyDefinition();
+            StringBuilder keyBuilder = null;
+            if (newDraft) {
+                keyBuilder = new StringBuilder("=");
+            }
+
             for (final QName listKey : listKeys) {
                 final DataSchemaNode dataChildByName = ((DataNodeContainer) schemaNode).getDataChildByName(listKey);
                 pathListParams.add(((LeafSchemaNode) dataChildByName));
-
-                final String pathParamIdentifier = new StringBuilder("/{").append(listKey.getLocalName()).append("}")
-                        .toString();
+                final String pathParamIdentifier;
+                if (newDraft) {
+                    pathParamIdentifier = keyBuilder.append("{").append(listKey.getLocalName()).append("}")
+                            .toString();
+                } else {
+                    pathParamIdentifier = "/{" + listKey.getLocalName() + "}";
+                }
                 path.append(pathParamIdentifier);
 
                 final Parameter pathParam = new Parameter();
@@ -358,6 +392,9 @@ public class BaseYangSwaggerGenerator {
                 pathParam.setParamType("path");
 
                 pathParams.add(pathParam);
+                if (newDraft) {
+                    keyBuilder = new StringBuilder(",");
+                }
             }
         }
         return path.toString();
@@ -365,7 +402,7 @@ public class BaseYangSwaggerGenerator {
 
     protected void addRpcs(final RpcDefinition rpcDefn, final List<Api> apis, final String parentPath, final SchemaContext schemaContext) {
         final Api rpc = new Api();
-        final String resourcePath = parentPath + resolvePathArgumentsName(rpcDefn, schemaContext);
+        final String resourcePath = parentPath + "/" + resolvePathArgumentsName(rpcDefn, schemaContext);
         rpc.setPath(resourcePath);
 
         final Operation operationSpec = new Operation();
@@ -415,4 +452,7 @@ public class BaseYangSwaggerGenerator {
         return sortedModules;
     }
 
+    public void setDraft(final boolean draft) {
+        this.newDraft = draft;
+    }
 }
index 24768e836864ff76261c9dff5a102153304cdd86..b7fcdd0d8b721d8c150067830fef2f934275bf00 100644 (file)
@@ -9,11 +9,15 @@ package org.opendaylight.netconf.sal.rest.doc.impl;
 
 import static org.opendaylight.netconf.sal.rest.doc.util.RestDocgenUtil.resolveNodesName;
 
-import com.google.common.base.Preconditions;
+import com.google.common.base.Optional;
+import com.mifmif.common.regex.Generex;
 import java.io.IOException;
+import java.net.URI;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Pattern;
 import javax.annotation.concurrent.NotThreadSafe;
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -21,7 +25,6 @@ import org.json.JSONObject;
 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder;
 import org.opendaylight.netconf.sal.rest.doc.model.builder.OperationBuilder.Post;
 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ConstraintDefinition;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
@@ -32,23 +35,30 @@ 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.RevisionAwareXPath;
 import org.opendaylight.yangtools.yang.model.api.RpcDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.type.BinaryTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition.Bit;
 import org.opendaylight.yangtools.yang.model.api.type.BooleanTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.EmptyTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition.EnumPair;
 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
+import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
 import org.opendaylight.yangtools.yang.model.api.type.StringTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.UnionTypeDefinition;
 import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
+import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -60,10 +70,10 @@ public class ModelGenerator {
 
     private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class);
 
+    private static final Pattern STRIP_PATTERN = Pattern.compile("\\[[^\\[\\]]*\\]");
     private static final String BASE_64 = "base64";
     private static final String BINARY_ENCODING_KEY = "binaryEncoding";
     private static final String MEDIA_KEY = "media";
-    private static final String ONE_OF_KEY = "oneOf";
     private static final String UNIQUE_ITEMS_KEY = "uniqueItems";
     private static final String MAX_ITEMS = "maxItems";
     private static final String MIN_ITEMS = "minItems";
@@ -80,55 +90,42 @@ public class ModelGenerator {
     private static final String OBJECT_TYPE = "object";
     private static final String ARRAY_TYPE = "array";
     private static final String ENUM = "enum";
-    private static final String INTEGER = "integer";
-    private static final String NUMBER = "number";
-    private static final String BOOLEAN = "boolean";
-    private static final String STRING = "string";
     private static final String ID_KEY = "id";
     private static final String SUB_TYPES_KEY = "subTypes";
+    private static final String UNIQUE_EMPTY_IDENTIFIER = "unique_empty_identifier";
 
     private Module topLevelModule;
 
     public ModelGenerator() {
     }
 
-    private static String jsonTypeFor(final TypeDefinition<?> type) {
-        if (type instanceof BooleanTypeDefinition) {
-            return BOOLEAN;
-        } else if (type instanceof DecimalTypeDefinition) {
-            return NUMBER;
-        } else if (type instanceof EnumTypeDefinition) {
-            return ENUM;
-        } else if (type instanceof IntegerTypeDefinition) {
-            return INTEGER;
-        } else if (type instanceof UnsignedIntegerTypeDefinition) {
-            return INTEGER;
-        } else if (type instanceof StringTypeDefinition) {
-            return STRING;
-        }
-
-        // TODO: Binary type
-        return null;
-    }
-
+    /**
+     * Creates Json models from provided module according to swagger spec
+     *
+     * @param module        - Yang module to be converted
+     * @param schemaContext - SchemaContext of all Yang files used by Api Doc
+     * @return JSONObject containing data used for creating examples and models in Api Doc
+     * @throws IOException
+     * @throws JSONException
+     */
     public JSONObject convertToJsonSchema(final Module module, final SchemaContext schemaContext) throws IOException, JSONException {
         final JSONObject models = new JSONObject();
+        models.put(UNIQUE_EMPTY_IDENTIFIER, new JSONObject());
         topLevelModule = module;
-        processModules(module, models);
+        processModules(module, models, schemaContext);
         processContainersAndLists(module, models, schemaContext);
         processRPCs(module, models, schemaContext);
         processIdentities(module, models);
         return models;
     }
 
-    private void processModules(final Module module, final JSONObject models) throws JSONException {
+    private void processModules(final Module module, final JSONObject models, final SchemaContext schemaContext) throws JSONException {
         createConcreteModelForPost(models, module.getName() + BaseYangSwaggerGenerator.MODULE_NAME_SUFFIX,
-                createPropertiesForPost(module, module.getName()));
+                createPropertiesForPost(module, schemaContext, module.getName()));
     }
 
     private void processContainersAndLists(final Module module, final JSONObject models, final SchemaContext schemaContext)
             throws IOException, JSONException {
-
         final String moduleName = module.getName();
 
         for (final DataSchemaNode childNode : module.getChildNodes()) {
@@ -161,7 +158,7 @@ public class ModelGenerator {
                 final JSONObject childSchema = getSchemaTemplate();
                 childSchema.put(TYPE_KEY, OBJECT_TYPE);
                 childSchema.put(PROPERTIES_KEY, properties);
-                childSchema.put("id", filename);
+                childSchema.put(ID_KEY, filename);
                 models.put(filename, childSchema);
 
                 processTopData(filename, models, input);
@@ -174,7 +171,7 @@ public class ModelGenerator {
                 final JSONObject childSchema = getSchemaTemplate();
                 childSchema.put(TYPE_KEY, OBJECT_TYPE);
                 childSchema.put(PROPERTIES_KEY, properties);
-                childSchema.put("id", filename);
+                childSchema.put(ID_KEY, filename);
                 models.put(filename, childSchema);
 
                 processTopData(filename, models, output);
@@ -182,7 +179,7 @@ public class ModelGenerator {
         }
     }
 
-    private static JSONObject processTopData(final String filename, final JSONObject models, final SchemaNode schemaNode) {
+    private JSONObject processTopData(final String filename, final JSONObject models, final SchemaNode schemaNode) {
         final JSONObject items = new JSONObject();
 
         items.put(REF_KEY, filename);
@@ -192,7 +189,7 @@ public class ModelGenerator {
 
         dataNodeProperties.putOpt(DESCRIPTION_KEY, schemaNode.getDescription());
         final JSONObject properties = new JSONObject();
-        properties.put(schemaNode.getQName().getLocalName(), dataNodeProperties);
+        properties.put(topLevelModule.getName() + ":" + schemaNode.getQName().getLocalName(), dataNodeProperties);
         final JSONObject finalChildSchema = getSchemaTemplate();
         finalChildSchema.put(TYPE_KEY, OBJECT_TYPE);
         finalChildSchema.put(PROPERTIES_KEY, properties);
@@ -205,10 +202,8 @@ public class ModelGenerator {
     /**
      * Processes the 'identity' statement in a yang model and maps it to a 'model' in the Swagger JSON spec.
      *
-     * @param module
-     *            The module from which the identity stmt will be processed
-     * @param models
-     *            The JSONObject in which the parsed identity will be put as a 'model' obj
+     * @param module The module from which the identity stmt will be processed
+     * @param models The JSONObject in which the parsed identity will be put as a 'model' obj
      */
     private static void processIdentities(final Module module, final JSONObject models) throws JSONException {
 
@@ -240,6 +235,7 @@ public class ModelGenerator {
                         subTypes.put(derivedId.getQName().getLocalName());
                     }
                     identityObj.put(SUB_TYPES_KEY, subTypes);
+
                 }
             } else {
                 /**
@@ -257,7 +253,6 @@ public class ModelGenerator {
     private JSONObject processDataNodeContainer(final DataNodeContainer dataNode, final String parentName, final JSONObject models,
                                                 final boolean isConfig, final SchemaContext schemaContext) throws JSONException, IOException {
         if (dataNode instanceof ListSchemaNode || dataNode instanceof ContainerSchemaNode) {
-            Preconditions.checkArgument(dataNode instanceof SchemaNode, "Data node should be also schema node");
             final Iterable<DataSchemaNode> containerChildren = dataNode.getChildNodes();
             final String localName = ((SchemaNode) dataNode).getQName().getLocalName();
             final JSONObject properties = processChildren(containerChildren, parentName + "/" + localName, models, isConfig, schemaContext);
@@ -268,12 +263,12 @@ public class ModelGenerator {
             childSchema.put(TYPE_KEY, OBJECT_TYPE);
             childSchema.put(PROPERTIES_KEY, properties);
 
-            childSchema.put("id", nodeName);
+            childSchema.put(ID_KEY, nodeName);
             models.put(nodeName, childSchema);
 
             if (isConfig) {
                 createConcreteModelForPost(models, localName,
-                        createPropertiesForPost(dataNode, parentName + "/" + localName));
+                        createPropertiesForPost(dataNode, schemaContext, parentName + "/" + localName));
             }
 
             return processTopData(nodeName, models, (SchemaNode) dataNode);
@@ -282,16 +277,16 @@ public class ModelGenerator {
     }
 
     private static void createConcreteModelForPost(final JSONObject models, final String localName,
-            final JSONObject properties) throws JSONException {
+                                                   final JSONObject properties) throws JSONException {
         final String nodePostName = OperationBuilder.CONFIG + localName + Post.METHOD_NAME;
         final JSONObject postSchema = getSchemaTemplate();
         postSchema.put(TYPE_KEY, OBJECT_TYPE);
-        postSchema.put("id", nodePostName);
+        postSchema.put(ID_KEY, nodePostName);
         postSchema.put(PROPERTIES_KEY, properties);
         models.put(nodePostName, postSchema);
     }
 
-    private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer, final String parentName)
+    private JSONObject createPropertiesForPost(final DataNodeContainer dataNodeContainer, final SchemaContext schemaContext, final String parentName)
             throws JSONException {
         final JSONObject properties = new JSONObject();
         for (final DataSchemaNode childNode : dataNodeContainer.getChildNodes()) {
@@ -303,18 +298,13 @@ public class ModelGenerator {
                 property.put(ITEMS_KEY, items);
                 properties.put(childNode.getQName().getLocalName(), property);
             } else if (childNode instanceof LeafSchemaNode) {
-                final JSONObject property = processLeafNode((LeafSchemaNode) childNode);
+                final JSONObject property = processLeafNode((LeafSchemaNode) childNode, schemaContext);
                 properties.put(childNode.getQName().getLocalName(), property);
             }
         }
         return properties;
     }
 
-    private JSONObject processChildren(final Iterable<DataSchemaNode> nodes, final String moduleName,
-                                       final JSONObject models, final SchemaContext schemaContext) throws JSONException, IOException {
-        return processChildren(nodes, moduleName, models, true, schemaContext);
-    }
-
     /**
      * Processes the nodes.
      */
@@ -327,17 +317,21 @@ public class ModelGenerator {
                 final String name = resolveNodesName(node, topLevelModule, schemaContext);
                 final JSONObject property;
                 if (node instanceof LeafSchemaNode) {
-                    property = processLeafNode((LeafSchemaNode) node);
+                    property = processLeafNode((LeafSchemaNode) node, schemaContext);
 
                 } else if (node instanceof ListSchemaNode) {
                     property = processDataNodeContainer((ListSchemaNode) node, parentName, models, isConfig,
                             schemaContext);
 
                 } else if (node instanceof LeafListSchemaNode) {
-                    property = processLeafListNode((LeafListSchemaNode) node);
+                    property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
 
                 } else if (node instanceof ChoiceSchemaNode) {
-                    property = processChoiceNode((ChoiceSchemaNode) node, parentName, models, schemaContext);
+                    if (((ChoiceSchemaNode) node).getCases().iterator().hasNext()) {
+                        processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next().getChildNodes(),
+                                parentName, models, schemaContext, isConfig, properties);
+                    }
+                    continue;
 
                 } else if (node instanceof AnyXmlSchemaNode) {
                     property = processAnyXMLNode((AnyXmlSchemaNode) node);
@@ -350,46 +344,70 @@ public class ModelGenerator {
                     throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
                 }
                 property.putOpt(DESCRIPTION_KEY, node.getDescription());
-                properties.put(name, property);
+                properties.put(topLevelModule.getName() + ":" + name, property);
             }
         }
         return properties;
     }
 
-    private JSONObject processLeafListNode(final LeafListSchemaNode listNode) throws JSONException {
+    private JSONObject processLeafListNode(final LeafListSchemaNode listNode, final SchemaContext schemaContext) throws JSONException {
         final JSONObject props = new JSONObject();
         props.put(TYPE_KEY, ARRAY_TYPE);
 
         final JSONObject itemsVal = new JSONObject();
-        processTypeDef(listNode.getType(), itemsVal);
+        final ConstraintDefinition constraints = listNode.getConstraints();
+        final Optional<Integer> maxOptional = Optional.fromNullable(constraints.getMaxElements());
+        if (maxOptional.or(2) >= 2) {
+            processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+            processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+        } else {
+            processTypeDef(listNode.getType(), listNode, itemsVal, schemaContext);
+        }
         props.put(ITEMS_KEY, itemsVal);
 
-        final ConstraintDefinition constraints = listNode.getConstraints();
+
         processConstraints(constraints, props);
 
         return props;
     }
 
-    private JSONObject processChoiceNode(final ChoiceSchemaNode choiceNode, final String parentName, final JSONObject models,
-                                         final SchemaContext schemaContext) throws JSONException, IOException {
+    private void processChoiceNode(final Iterable<DataSchemaNode> nodes, final String moduleName, final JSONObject models,
+                                   final SchemaContext schemaContext, final boolean isConfig, final JSONObject properties)
+            throws JSONException, IOException {
+        for (final DataSchemaNode node : nodes) {
+            final String name = resolveNodesName(node, topLevelModule, schemaContext);
+            final JSONObject property;
 
-        final Set<ChoiceCaseNode> cases = choiceNode.getCases();
+            if (node instanceof LeafSchemaNode) {
+                property = processLeafNode((LeafSchemaNode) node, schemaContext);
 
-        final JSONArray choiceProps = new JSONArray();
-        for (final ChoiceCaseNode choiceCase : cases) {
-            final String choiceName = choiceCase.getQName().getLocalName();
-            final JSONObject choiceProp = processChildren(choiceCase.getChildNodes(), parentName, models, schemaContext);
-            final JSONObject choiceObj = new JSONObject();
-            choiceObj.put(choiceName, choiceProp);
-            choiceObj.put(TYPE_KEY, OBJECT_TYPE);
-            choiceProps.put(choiceObj);
-        }
+            } else if (node instanceof ListSchemaNode) {
+                property = processDataNodeContainer((ListSchemaNode) node, moduleName, models, isConfig,
+                        schemaContext);
+
+            } else if (node instanceof LeafListSchemaNode) {
+                property = processLeafListNode((LeafListSchemaNode) node, schemaContext);
 
-        final JSONObject oneOfProps = new JSONObject();
-        oneOfProps.put(ONE_OF_KEY, choiceProps);
-        oneOfProps.put(TYPE_KEY, OBJECT_TYPE);
+            } else if (node instanceof ChoiceSchemaNode) {
+                if (((ChoiceSchemaNode) node).getCases().iterator().hasNext())
+                    processChoiceNode(((ChoiceSchemaNode) node).getCases().iterator().next().getChildNodes(),
+                            moduleName, models, schemaContext, isConfig, properties);
+                continue;
 
-        return oneOfProps;
+            } else if (node instanceof AnyXmlSchemaNode) {
+                property = processAnyXMLNode((AnyXmlSchemaNode) node);
+
+            } else if (node instanceof ContainerSchemaNode) {
+                property = processDataNodeContainer((ContainerSchemaNode) node, moduleName, models, isConfig,
+                        schemaContext);
+
+            } else {
+                throw new IllegalArgumentException("Unknown DataSchemaNode type: " + node.getClass());
+            }
+
+            property.putOpt(DESCRIPTION_KEY, node.getDescription());
+            properties.put(name, property);
+        }
     }
 
     private static void processConstraints(final ConstraintDefinition constraints, final JSONObject props) throws JSONException {
@@ -406,14 +424,13 @@ public class ModelGenerator {
         }
     }
 
-    private JSONObject processLeafNode(final LeafSchemaNode leafNode) throws JSONException {
+    private JSONObject processLeafNode(final LeafSchemaNode leafNode, final SchemaContext schemaContext) throws JSONException {
         final JSONObject property = new JSONObject();
 
         final String leafDescription = leafNode.getDescription();
         property.put(DESCRIPTION_KEY, leafDescription);
-
         processConstraints(leafNode.getConstraints(), property);
-        processTypeDef(leafNode.getType(), property);
+        processTypeDef(leafNode.getType(), leafNode, property, schemaContext);
 
         return property;
     }
@@ -425,67 +442,122 @@ public class ModelGenerator {
         property.put(DESCRIPTION_KEY, leafDescription);
 
         processConstraints(leafNode.getConstraints(), property);
+        final String localName = leafNode.getQName().getLocalName();
+        property.put(TYPE_KEY, "example of anyxml " + localName);
 
         return property;
     }
 
-    private void processTypeDef(final TypeDefinition<?> leafTypeDef, final JSONObject property) throws JSONException {
-        if (leafTypeDef instanceof BinaryTypeDefinition) {
-            processBinaryType((BinaryTypeDefinition) leafTypeDef, property);
-        } else if (leafTypeDef instanceof BitsTypeDefinition) {
-            processBitsType((BitsTypeDefinition) leafTypeDef, property);
-        } else if (leafTypeDef instanceof EnumTypeDefinition) {
-            processEnumType((EnumTypeDefinition) leafTypeDef, property);
-        } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
-            property.putOpt(TYPE_KEY,
-                    ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName());
-        } else if (leafTypeDef instanceof StringTypeDefinition) {
-            processStringType((StringTypeDefinition) leafTypeDef, property);
-        } else if (leafTypeDef instanceof UnionTypeDefinition) {
-            processUnionType((UnionTypeDefinition) leafTypeDef, property);
-        } else {
-            String jsonType = jsonTypeFor(leafTypeDef);
-            if (jsonType == null) {
-                jsonType = "object";
+    private String processTypeDef(final TypeDefinition<?> leafTypeDef, final DataSchemaNode node,
+                                  final JSONObject property, final SchemaContext schemaContext) throws JSONException {
+        final String jsonType;
+        if (leafTypeDef.getDefaultValue() == null) {
+            if (leafTypeDef instanceof BinaryTypeDefinition) {
+                jsonType = processBinaryType(property);
+
+            } else if (leafTypeDef instanceof BitsTypeDefinition) {
+                jsonType = processBitsType((BitsTypeDefinition) leafTypeDef, property);
+
+            } else if (leafTypeDef instanceof EnumTypeDefinition) {
+                jsonType = processEnumType((EnumTypeDefinition) leafTypeDef, property);
+
+            } else if (leafTypeDef instanceof IdentityrefTypeDefinition) {
+                final String name = topLevelModule.getName();
+                jsonType = name + ":" + ((IdentityrefTypeDefinition) leafTypeDef).getIdentity().getQName().getLocalName();
+
+            } else if (leafTypeDef instanceof StringTypeDefinition) {
+                jsonType = processStringType(leafTypeDef, property, node.getQName().getLocalName());
+
+            } else if (leafTypeDef instanceof UnionTypeDefinition) {
+                jsonType = processUnionType((UnionTypeDefinition) leafTypeDef, property, schemaContext, node);
+
+            } else if (leafTypeDef instanceof EmptyTypeDefinition) {
+                jsonType = UNIQUE_EMPTY_IDENTIFIER;
+
+            } else if (leafTypeDef instanceof LeafrefTypeDefinition) {
+                return processLeafRef(node, property, schemaContext, leafTypeDef);
+
+            } else if (leafTypeDef instanceof BooleanTypeDefinition) {
+                jsonType = "true";
+
+            } else if (leafTypeDef instanceof DecimalTypeDefinition) {
+                jsonType = String.valueOf(((DecimalTypeDefinition) leafTypeDef).getRangeConstraints()
+                        .iterator().next().getMin());
+
+            } else if (leafTypeDef instanceof IntegerTypeDefinition) {
+                jsonType = String.valueOf(((IntegerTypeDefinition) leafTypeDef).getRangeConstraints()
+                        .iterator().next().getMin());
+
+            } else if (leafTypeDef instanceof UnsignedIntegerTypeDefinition) {
+                jsonType = String.valueOf(((UnsignedIntegerTypeDefinition) leafTypeDef).getRangeConstraints()
+                        .iterator().next().getMin());
+
+            } else {
+                jsonType = OBJECT_TYPE;
+
             }
-            property.putOpt(TYPE_KEY, jsonType);
+        } else {
+            jsonType = String.valueOf(leafTypeDef.getDefaultValue());
         }
+        property.putOpt(TYPE_KEY, jsonType);
+        return jsonType;
     }
 
-    private static void processBinaryType(final BinaryTypeDefinition binaryType, final JSONObject property) throws JSONException {
-        property.put(TYPE_KEY, STRING);
+    private String processLeafRef(final DataSchemaNode node, final JSONObject property, final SchemaContext schemaContext,
+                                  final TypeDefinition<?> leafTypeDef) {
+        RevisionAwareXPath xPath = ((LeafrefTypeDefinition) leafTypeDef).getPathStatement();
+        final URI namespace = leafTypeDef.getQName().getNamespace();
+        final Date revision = leafTypeDef.getQName().getRevision();
+        final Module module = schemaContext.findModuleByNamespaceAndRevision(namespace, revision);
+        final SchemaNode schemaNode;
+
+        final String xPathString = STRIP_PATTERN.matcher(xPath.toString()).replaceAll("");
+        xPath = new RevisionAwareXPathImpl(xPathString, xPath.isAbsolute());
+
+        if (xPath.isAbsolute()) {
+            schemaNode = SchemaContextUtil.findDataSchemaNode(schemaContext, module, xPath);
+        } else {
+            schemaNode = SchemaContextUtil.findDataSchemaNodeForRelativeXPath(schemaContext, module, node, xPath);
+        }
+
+        return processTypeDef(((TypedSchemaNode) schemaNode).getType(), (DataSchemaNode) schemaNode, property, schemaContext);
+    }
+
+    private static String processBinaryType(final JSONObject property) throws JSONException {
         final JSONObject media = new JSONObject();
         media.put(BINARY_ENCODING_KEY, BASE_64);
         property.put(MEDIA_KEY, media);
+        return "bin1 bin2";
     }
 
-    private static void processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
+    private static String processEnumType(final EnumTypeDefinition enumLeafType, final JSONObject property) throws JSONException {
         final List<EnumPair> enumPairs = enumLeafType.getValues();
         final List<String> enumNames = new ArrayList<>();
         for (final EnumPair enumPair : enumPairs) {
             enumNames.add(enumPair.getName());
         }
+
         property.putOpt(ENUM, new JSONArray(enumNames));
+        return enumLeafType.getValues().iterator().next().getName();
     }
 
-    private static void processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
-        property.put(TYPE_KEY, ARRAY_TYPE);
+    private static String processBitsType(final BitsTypeDefinition bitsType, final JSONObject property) throws JSONException {
         property.put(MIN_ITEMS, 0);
         property.put(UNIQUE_ITEMS_KEY, true);
-        final JSONArray enumValues = new JSONArray();
-
+        final List<String> enumNames = new ArrayList<>();
         final List<Bit> bits = bitsType.getBits();
         for (final Bit bit : bits) {
-            enumValues.put(bit.getName());
+            enumNames.add(bit.getName());
         }
-        final JSONObject itemsValue = new JSONObject();
-        itemsValue.put(ENUM, enumValues);
-        property.put(ITEMS_KEY, itemsValue);
+        property.put(ENUM, new JSONArray(enumNames));
+
+        return enumNames.iterator().next() + " " + enumNames.get(enumNames.size() - 1);
     }
 
-    private static void processStringType(final StringTypeDefinition stringType, final JSONObject property) throws JSONException {
-        StringTypeDefinition type = stringType;
-        List<LengthConstraint> lengthConstraints = stringType.getLengthConstraints();
+    private static String processStringType(final TypeDefinition<?> stringType, final JSONObject property, final String nodeName)
+            throws JSONException {
+        StringTypeDefinition type = (StringTypeDefinition) stringType;
+        List<LengthConstraint> lengthConstraints = ((StringTypeDefinition) stringType).getLengthConstraints();
         while (lengthConstraints.isEmpty() && type.getBaseType() != null) {
             type = type.getBaseType();
             lengthConstraints = type.getLengthConstraints();
@@ -499,20 +571,26 @@ public class ModelGenerator {
             property.putOpt(MIN_LENGTH_KEY, min);
             property.putOpt(MAX_LENGTH_KEY, max);
         }
-
-        property.put(TYPE_KEY, STRING);
+        if (type.getPatternConstraints().iterator().hasNext()) {
+            final PatternConstraint pattern = type.getPatternConstraints().iterator().next();
+            String regex = pattern.getRegularExpression();
+            regex = regex.substring(1, regex.length() - 1);
+            final Generex generex = new Generex(regex);
+            return generex.random();
+        } else {
+            return "Some " + nodeName;
+        }
     }
 
-    private static void processUnionType(final UnionTypeDefinition unionType, final JSONObject property) throws JSONException {
-        final StringBuilder type = new StringBuilder();
+    private String processUnionType(final UnionTypeDefinition unionType, final JSONObject property,
+                                    final SchemaContext schemaContext, final DataSchemaNode node)
+            throws JSONException {
+        final List<String> unionNames = new ArrayList<>();
         for (final TypeDefinition<?> typeDef : unionType.getTypes()) {
-            if (type.length() > 0) {
-                type.append(" or ");
-            }
-            type.append(jsonTypeFor(typeDef));
+            unionNames.add(processTypeDef(typeDef, node, property, schemaContext));
         }
-
-        property.put(TYPE_KEY, type);
+        property.put(ENUM, new JSONArray(unionNames));
+        return unionNames.iterator().next();
     }
 
     /**
@@ -525,4 +603,4 @@ public class ModelGenerator {
         return schemaJSON;
     }
 
-}
+}
\ No newline at end of file
index 98268495b86e5ef8069301a20ed7bdddcf755234..11a4db0cf4da8304d1047647ea82482cb59ef79f 100644 (file)
@@ -39,6 +39,7 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
 
     private static final String DATASTORES_REVISION = "-";
     private static final String DATASTORES_LABEL = "Datastores";
+    private static final String RESTCONF_DRAFT = "17";
 
     private DOMMountPointService mountService;
     private final Map<YangInstanceIdentifier, Long> instanceIdToLongId = new TreeMap<>(
@@ -50,7 +51,7 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
 
     private static final AtomicReference<MountPointSwagger> selfRef = new AtomicReference<>();
     private SchemaService globalSchema;
-
+    private static boolean newDraft;
     public Map<String, Long> getInstanceIdentifiers() {
         final Map<String, Long> urlToId = new HashMap<>();
         synchronized (lock) {
@@ -80,20 +81,29 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
 
     private String generateUrlPrefixFromInstanceID(final YangInstanceIdentifier key, final String moduleName) {
         final StringBuilder builder = new StringBuilder();
+        builder.append("/");
         if (moduleName != null) {
-            builder.append(moduleName);
-            builder.append(':');
+            builder.append(moduleName)
+                    .append(':');
         }
         for (final PathArgument arg : key.getPathArguments()) {
             final String name = arg.getNodeType().getLocalName();
             if (arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates) {
                 final NodeIdentifierWithPredicates nodeId = (NodeIdentifierWithPredicates) arg;
                 for (final Entry<QName, Object> entry : nodeId.getKeyValues().entrySet()) {
-                    builder.append(entry.getValue()).append('/');
+                    if (newDraft) {
+                        builder.deleteCharAt(builder.length() - 1)
+                                .append("=")
+                                .append(entry.getValue())
+                                .append('/');
+                    } else {
+                        builder.append(entry.getValue())
+                                .append('/');
+                    }
                 }
             } else {
-                builder.append(name);
-                builder.append('/');
+                builder.append(name)
+                        .append('/');
             }
         }
         return builder.toString();
@@ -101,7 +111,7 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
 
     private String getYangMountUrl(final YangInstanceIdentifier key) {
         final String modName = findModuleName(key, globalSchema.getGlobalContext());
-        return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount/";
+        return generateUrlPrefixFromInstanceID(key, modName) + "yang-ext:mount";
     }
 
     public ResourceList getResourceList(final UriInfo uriInfo, final Long id) {
@@ -187,7 +197,7 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
         getConfig.setNotes(note);
 
         final Api api = new Api();
-        api.setPath(getDataStorePath("/" + datastore + "/", context));
+        api.setPath(getDataStorePath(datastore, context).concat(getContent(datastore)));
         api.setOperations(Collections.singletonList(getConfig));
 
         return api;
@@ -220,7 +230,17 @@ public class MountPointSwagger extends BaseYangSwaggerGenerator implements Mount
             selfRef.compareAndSet(null, new MountPointSwagger());
             swagger = selfRef.get();
         }
+        newDraft = false;
         return swagger;
     }
 
+    public static MountPointSwagger getInstanceDraft17() {
+        MountPointSwagger swagger = selfRef.get();
+        if (swagger == null) {
+            selfRef.compareAndSet(null, new MountPointSwagger());
+            swagger = selfRef.get();
+        }
+        newDraft = true;
+        return swagger;
+    }
 }
diff --git a/restconf/sal-rest-docgen/src/main/resources/17/explorer/index.html b/restconf/sal-rest-docgen/src/main/resources/17/explorer/index.html
new file mode 100644 (file)
index 0000000..d5126dd
--- /dev/null
@@ -0,0 +1,127 @@
+<!DOCTYPE html>\r
+<!--\r
+  ~ Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.\r
+  ~\r
+  ~ This program and the accompanying materials are made available under the\r
+  ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,\r
+  ~ and is available at http://www.eclipse.org/legal/epl-v10.html\r
+  -->\r
+\r
+<html>\r
+<head>\r
+    <title>RestConf Documentation</title>\r
+    <link href='//fonts.googleapis.com/css?family=Droid+Sans:400,700'\r
+          rel='stylesheet' type='text/css'/>\r
+    <link href='../../explorer/css/highlight.default.css' media='screen' rel='stylesheet'\r
+          type='text/css'/>\r
+    <link href='../../explorer/css/screen.css' media='screen' rel='stylesheet'\r
+          type='text/css'/>\r
+    <link rel="stylesheet" type="text/css" href="../../explorer/css/opendaylight.css">\r
+    <link rel="stylesheet" type="text/css"\r
+          href="../../explorer/css/ui-lightness/jquery-ui-1.10.4.custom.min.css">\r
+    <script type="text/javascript" src="../../explorer/lib/shred.bundle.js"></script>\r
+    <script src='../../explorer/lib/jquery-1.8.0.min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/jquery-ui-1.11.0.min.js' type="text/javascript"></script>\r
+    <script src='../../explorer/lib/jquery.slideto.min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/jquery.wiggle.min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/handlebars-1.0.0.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/underscore-min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/backbone-min.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/swagger.js' type='text/javascript'></script>\r
+    <script src='../../explorer/swagger-ui.js' type='text/javascript'></script>\r
+    <script src='lib/odl/list_mounts.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/highlight.7.3.pack.js' type='text/javascript'></script>\r
+    <script src='../../explorer/lib/odl/swagger.js' type='text/javascript'></script>\r
+\r
+    <script type="text/javascript">\r
+       \r
+       //reloads the swagger UI documentation for the specified mount.\r
+       var loadMount = function(mountIndex, mountPath) {\r
+               $("#message").empty();\r
+               $("#message").append( "<p>Loading...</p>" );\r
+               loadSwagger("/apidoc/17/apis/mounts/" + mountIndex,\r
+                               "swagger-ui-container");\r
+               $("#message").empty();\r
+               $("#message").append( "<h2><b>Showing mount points for " + mountPath + "</b></h2>");\r
+       }\r
+\r
+       //clears the swagger UI and adds text prompting use to select a mount point.\r
+    var selectAMount = function(string) {\r
+        $("#swagger-ui-container").empty();\r
+        $("#message").empty();\r
+        $("#message").append("<p>Select a mount point.</p>");\r
+    }\r
+    \r
+       //loads the root swagger documenation (which comes from RestConf)\r
+       var loadRootSwagger = function() {\r
+               $("#message").empty();\r
+               loadSwagger("/apidoc/17/apis", "swagger-ui-container");\r
+       }\r
+\r
+       //main method to initialize the mount list / swagger docs / tabs on page load\r
+       $(function() {\r
+               $("#tabs").tabs();\r
+\r
+               loadMountList($("#mountlist"));\r
+\r
+               loadRootSwagger();\r
+       });\r
+    </script>\r
+</head>\r
+\r
+<body>\r
+<div>\r
+    <!-- style="background-color: #FCA000;" -->\r
+    <div class="swagger-ui-wrap ui-tabs">\r
+        <table>\r
+            <tr>\r
+                <td><img src="../../explorer/images/logo_small.png"/></td>\r
+                <td><h1 width="100%">OpenDaylight RestConf API\r
+                    Documentation</h1></td>\r
+            </tr>\r
+        </table>\r
+    </div>\r
+</div>\r
+\r
+<div class="navbar-inner">\r
+    <div class="brand"></div>\r
+</div>\r
+\r
+<!--  defines the div shells which represent the jquery tabs -->\r
+<div id="tabs" class="swagger-ui-wrap">\r
+    <ul>\r
+        <li><a href="#Controller" onclick="loadRootSwagger()">Controller\r
+            Resources</a></li>\r
+        <li><a href="#Mounts" onclick="selectAMount()">Mounted\r
+            Resources</a></li>\r
+    </ul>\r
+\r
+    <div id="Controller">\r
+        <div>\r
+            <h3>Below are the list of APIs supported by the Controller.</h3>\r
+        </div>\r
+    </div>\r
+    <div id="Mounts">\r
+        <div>\r
+            <h3>Mount Points - Select an API below for details on available\r
+                queries.</h3>\r
+        </div>\r
+        <div id="mountlist"></div>\r
+    </div>\r
+</div>\r
+\r
+<div class="swagger-ui-wrap">\r
+    <hr/>\r
+</div>\r
+\r
+<!-- messages -->\r
+<div id="message" class="swagger-ui-wrap"></div>\r
+\r
+<!-- the swagger is always loaded in this div -->\r
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>\r
+\r
+<div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>\r
+</body>\r
+\r
+</html>\r
diff --git a/restconf/sal-rest-docgen/src/main/resources/17/explorer/lib/odl/list_mounts.js b/restconf/sal-rest-docgen/src/main/resources/17/explorer/lib/odl/list_mounts.js
new file mode 100644 (file)
index 0000000..b9b4c62
--- /dev/null
@@ -0,0 +1,22 @@
+//constructs a table of mount points via a json response
+//Loads the table into the given dom.
+var loadMountList = function( dom ) {
+    dom.empty();
+    dom.append( "<p>Loading. Please wait...</p>" );
+    $.ajax( {
+        url: "/apidoc/17/apis/mounts",
+        datatype: 'jsonp',
+        success: function( strData ){
+            var myData = strData;
+            var list = $( "<table></table>" );
+            for( var key in myData )
+            {
+                list.append( "<tr><td><a href=\"#\" onclick=\"loadMount(" + 
+                                        myData[key].id + ", '" + myData[key].instance + "')\">" +
+                                        myData[key].instance + "</a></td></tr>");
+            }
+            dom.empty();
+            dom.append( list );
+        }
+    } );
+}
\ No newline at end of file
diff --git a/restconf/sal-rest-docgen/src/main/resources/17/explorer/static/index.html b/restconf/sal-rest-docgen/src/main/resources/17/explorer/static/index.html
new file mode 100644 (file)
index 0000000..4fb8f22
--- /dev/null
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+  ~ Copyright (c) 2016 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
+  -->
+
+<html>
+<head>
+    <title>RestConf Documentation</title>
+    <link href='../../../eplorer/static/fonts.css'
+          rel='stylesheet' type='text/css' />   <!--original location: //fonts.googleapis.com/css?family=Droid+Sans:400,700 -->
+    <link href='../../../explorer/css/highlight.default.css' media='screen' rel='stylesheet'
+          type='text/css' />
+    <link href='../../../explorer/css/screen.css' media='screen' rel='stylesheet'
+          type='text/css' />
+    <link rel="stylesheet" type="text/css" href="../../../eplorer/static/opendaylight.css">
+    <link rel="stylesheet" type="text/css"
+          href="../../../explorer/css/ui-lightness/jquery-ui-1.10.4.custom.min.css">
+    <script type="text/javascript" src="../../../explorer/lib/shred.bundle.js"></script>
+    <script src='../../../explorer/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/jquery-ui-1.11.0.min.js' type="text/javascript"></script>
+    <script src='../../../explorer/lib/jquery.slideto.min.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/jquery.wiggle.min.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/handlebars-1.0.0.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/underscore-min.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/backbone-min.js' type='text/javascript'></script>
+    <script src='../../explorer/swagger.js' type='text/javascript'></script>
+    <script src='../../explorer/swagger-ui.js' type='text/javascript'></script>
+    <script src='../lib/odl/list_mounts.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/highlight.7.3.pack.js' type='text/javascript'></script>
+    <script src='../../../explorer/lib/odl/swagger.js' type='text/javascript'></script>
+    <script src='resources.js' type='text/javascript'></script>
+
+    <script type="text/javascript">
+
+        //reloads the swagger UI documentation for the specified mount.
+        var loadMount = function(mountIndex, mountPath) {
+            $("#message").empty();
+            $("#message").append( "<p>Loading...</p>" );
+            loadSwagger("/apidoc/apis/mounts/" + mountIndex,
+                    "swagger-ui-container");
+            $("#message").empty();
+            $("#message").append( "<h2><b>Showing mount points for " + mountPath + "</b></h2>");
+        }
+
+        //clears the swagger UI and adds text prompting use to select a mount point.
+        var selectAMount = function(string) {
+            $("#swagger-ui-container").empty();
+            $("#message").empty();
+            $("#message").append("<p>Select a mount point.</p>");
+        }
+
+        //loads the root swagger documenation (which comes from RestConf)
+        var loadRootSwagger = function() {
+            $("#message").empty();
+            loadSwagger("/apidoc/apis", "swagger-ui-container");
+        }
+
+        //main method to initialize the mount list / swagger docs / tabs on page load
+        $(function() {
+            $("#tabs").tabs();
+
+            loadMountList($("#mountlist"));
+
+            loadRootSwagger();
+        });
+    </script>
+</head>
+
+<body>
+<div>
+    <!-- style="background-color: #FCA000;" -->
+    <div class="swagger-ui-wrap ui-tabs">
+        <table>
+            <tr>
+                <td><img src="../../../explorer/images/logo_small.png"/></td>
+                <td><h1 width="100%">OpenDaylight RestConf API
+                    Documentation</h1></td>
+            </tr>
+        </table>
+    </div>
+</div>
+
+<div class="navbar-inner">
+    <div class="brand"></div>
+</div>
+
+<!--  defines the div shells which represent the jquery tabs -->
+<div id="tabs" class="swagger-ui-wrap">
+    <ul>
+        <li><a href="#Controller" onclick="loadRootSwagger()">Controller
+            Resources</a></li>
+    </ul>
+
+    <div id="Controller">
+        <div>
+            <h3>Below are the list of APIs supported by the Controller.</h3>
+        </div>
+    </div>
+</div>
+
+<div class="swagger-ui-wrap"><hr/></div>
+
+<!-- messages -->
+<div id="message" class="swagger-ui-wrap"></div>
+
+<!-- the swagger is always loaded in this div -->
+<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
+
+<div id="message-bar" class="swagger-ui-wrap">&nbsp;</div>
+</body>
+
+</html>
index 7e72e8347c2024c1867f771e0754991cddab5926..0c279a57ace63bc1afdbfd17610c03f2ec3ce610 100644 (file)
@@ -3,6 +3,7 @@ This component offers Swagger documentation of the RestConf APIs.
 This Swagger documentation can be accessed in two ways:
 I. Running server
 Open a browser and go to http://<host>:8181/apidoc/explorer/index.html
+For new restconf draft go to http://<host>:8181/apidoc/17/explorer/index.html
 
 II. Static documentation generation
 By adding a reference to the StaticDocGenerator class in any pom.xml,
index cd267696304312eac072d295aba47b1529b6b343..849e2c90e39d397eb49c19e052e701433248806d 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="ISO-8859-1"?>
 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
-    version="3.0">
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
     <welcome-file-list>
         <welcome-file>index.html</welcome-file>
     </welcome-file-list>
@@ -16,8 +16,8 @@
     </servlet>
 
     <context-param>
-      <param-name>shiroEnvironmentClass</param-name>
-      <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
+        <param-name>shiroEnvironmentClass</param-name>
+        <param-value>org.opendaylight.aaa.shiro.web.env.KarafIniWebEnvironment</param-value>
     </context-param>
 
     <listener>
         <url-pattern>/apis/*</url-pattern>
     </servlet-mapping>
 
+    <servlet-mapping>
+        <servlet-name>JAXRSApiDoc</servlet-name>
+        <url-pattern>/17/apis/*</url-pattern>
+    </servlet-mapping>
+
     <filter>
         <filter-name>cross-origin-api-doc</filter-name>
         <filter-class>org.eclipse.jetty.servlets.CrossOriginFilter</filter-class>
         <filter-name>cross-origin-api-doc</filter-name>
         <url-pattern>/apis/*</url-pattern>
     </filter-mapping>
-
+    <filter-mapping>
+        <filter-name>cross-origin-api-doc</filter-name>
+        <url-pattern>/17/apis/*</url-pattern>
+    </filter-mapping>
 
     <security-constraint>
-      <web-resource-collection>
-        <web-resource-name>API Doc</web-resource-name>
-        <url-pattern>/*</url-pattern>
-      </web-resource-collection>
+        <web-resource-collection>
+            <web-resource-name>API Doc</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
     </security-constraint>
 
 </web-app>
index 020290508097db36cf21a3fb0f65fabd94d42b7b..ee1b1eea056a786939a3c055fe847c89b8e49a64 100644 (file)
@@ -37,9 +37,6 @@ import org.opendaylight.netconf.sal.rest.doc.swagger.ResourceList;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 
-/**
- *
- */
 public class ApiDocGeneratorTest {
 
     public static final String HTTP_HOST = "http://host";
@@ -55,6 +52,7 @@ public class ApiDocGeneratorTest {
     @Before
     public void setUp() throws Exception {
         this.generator = new ApiDocGenerator();
+        generator.setDraft(false);
         this.helper = new DocGenTestHelper();
         this.helper.setUp();
 
@@ -91,21 +89,21 @@ public class ApiDocGeneratorTest {
      */
     private void validateSwaggerApisForPost(final ApiDeclaration doc) {
         // two POST URI with concrete schema name in summary
-        final Api lstApi = findApi("/config/toaster2:lst/", doc);
-        assertNotNull("Api /config/toaster2:lst/ wasn't found", lstApi);
+        final Api lstApi = findApi("/config/toaster2:lst", doc);
+        assertNotNull("Api /config/toaster2:lst wasn't found", lstApi);
         assertTrue("POST for cont1 in lst is missing",
                 findOperation(lstApi.getOperations(), "POST", "(config)lstPOST", "toaster2/lst(config)lst1-TOP",
                         "toaster2/lst(config)cont1-TOP"));
 
-        final Api cont1Api = findApi("/config/toaster2:lst/cont1/", doc);
-        assertNotNull("Api /config/toaster2:lst/cont1/ wasn't found", cont1Api);
+        final Api cont1Api = findApi("/config/toaster2:lst/cont1", doc);
+        assertNotNull("Api /config/toaster2:lst/cont1 wasn't found", cont1Api);
         assertTrue("POST for cont11 in cont1 is missing",
                 findOperation(cont1Api.getOperations(), "POST", "(config)cont1POST", "toaster2/lst/cont1(config)cont11-TOP",
                         "toaster2/lst/cont1(config)lst11-TOP"));
 
         // no POST URI
-        final Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11/", doc);
-        assertNotNull("Api /config/toaster2:lst/cont1/cont11/ wasn't found", cont11Api);
+        final Api cont11Api = findApi("/config/toaster2:lst/cont1/cont11", doc);
+        assertNotNull("Api /config/toaster2:lst/cont1/cont11 wasn't found", cont11Api);
         assertTrue("POST operation shouldn't be present.", findOperations(cont11Api.getOperations(), "POST").isEmpty());
 
     }
@@ -114,7 +112,7 @@ public class ApiDocGeneratorTest {
      * Tries to find operation with name {@code operationName} and with summary {@code summary}
      */
     private boolean findOperation(final List<Operation> operations, final String operationName, final String type,
-            final String... searchedParameters) {
+                                  final String... searchedParameters) {
         final Set<Operation> filteredOperations = findOperations(operations, operationName);
         for (final Operation operation : filteredOperations) {
             if (operation.getType().equals(type)) {
@@ -172,18 +170,18 @@ public class ApiDocGeneratorTest {
             final JSONObject configLstTop = models.getJSONObject("toaster2(config)lst-TOP");
             assertNotNull(configLstTop);
 
-            containsReferences(configLstTop, "lst", "toaster2(config)");
+            containsReferences(configLstTop, "toaster2:lst", "toaster2(config)");
 
             final JSONObject configLst = models.getJSONObject("toaster2(config)lst");
             assertNotNull(configLst);
 
-            containsReferences(configLst, "lst1", "toaster2/lst(config)");
-            containsReferences(configLst, "cont1", "toaster2/lst(config)");
+            containsReferences(configLst, "toaster2:lst1", "toaster2/lst(config)");
+            containsReferences(configLst, "toaster2:cont1", "toaster2/lst(config)");
 
             final JSONObject configLst1Top = models.getJSONObject("toaster2/lst(config)lst1-TOP");
             assertNotNull(configLst1Top);
 
-            containsReferences(configLst1Top, "lst1", "toaster2/lst(config)");
+            containsReferences(configLst1Top, "toaster2:lst1", "toaster2/lst(config)");
 
             final JSONObject configLst1 = models.getJSONObject("toaster2/lst(config)lst1");
             assertNotNull(configLst1);
@@ -191,24 +189,24 @@ public class ApiDocGeneratorTest {
             final JSONObject configCont1Top = models.getJSONObject("toaster2/lst(config)cont1-TOP");
             assertNotNull(configCont1Top);
 
-            containsReferences(configCont1Top, "cont1", "toaster2/lst(config)");
+            containsReferences(configCont1Top, "toaster2:cont1", "toaster2/lst(config)");
             final JSONObject configCont1 = models.getJSONObject("toaster2/lst(config)cont1");
             assertNotNull(configCont1);
 
-            containsReferences(configCont1, "cont11", "toaster2/lst/cont1(config)");
-            containsReferences(configCont1, "lst11", "toaster2/lst/cont1(config)");
+            containsReferences(configCont1, "toaster2:cont11", "toaster2/lst/cont1(config)");
+            containsReferences(configCont1, "toaster2:lst11", "toaster2/lst/cont1(config)");
 
             final JSONObject configCont11Top = models.getJSONObject("toaster2/lst/cont1(config)cont11-TOP");
             assertNotNull(configCont11Top);
 
-            containsReferences(configCont11Top, "cont11", "toaster2/lst/cont1(config)");
+            containsReferences(configCont11Top, "toaster2:cont11", "toaster2/lst/cont1(config)");
             final JSONObject configCont11 = models.getJSONObject("toaster2/lst/cont1(config)cont11");
             assertNotNull(configCont11);
 
             final JSONObject configlst11Top = models.getJSONObject("toaster2/lst/cont1(config)lst11-TOP");
             assertNotNull(configlst11Top);
 
-            containsReferences(configlst11Top, "lst11", "toaster2/lst/cont1(config)");
+            containsReferences(configlst11Top, "toaster2:lst11", "toaster2/lst/cont1(config)");
             final JSONObject configLst11 = models.getJSONObject("toaster2/lst/cont1(config)lst11");
             assertNotNull(configLst11);
         } catch (final JSONException e) {
@@ -232,7 +230,7 @@ public class ApiDocGeneratorTest {
         assertNotNull(itemsInNodeInProperties);
 
         final String itemRef = itemsInNodeInProperties.getString("$ref");
-        assertEquals(prefix + childObject, itemRef);
+        assertEquals(prefix + childObject.split(":")[1], itemRef);
     }
 
     @Test
@@ -249,7 +247,7 @@ public class ApiDocGeneratorTest {
                 // testing bugs.opendaylight.org bug 1290. UnionType model type.
                 final String jsonString = doc.getModels().toString();
                 assertTrue(jsonString.contains(
-                        "testUnion\":{\"type\":\"integer or string\",\"required\":false}"));
+                        "testUnion\":{\"type\":\"-2147483648\",\"required\":false,\"enum\":[\"-2147483648\",\"Some testUnion\"]}"));
             }
         }
     }
@@ -267,12 +265,12 @@ public class ApiDocGeneratorTest {
 
                 final JSONObject models = doc.getModels();
                 final JSONObject inputTop = models.getJSONObject("(make-toast)input-TOP");
-                final String testString = "{\"input\":{\"type\":\"object\",\"items\":{\"$ref\":\"(make-toast)input\"}}}";
+                final String testString = "{\"toaster:input\":{\"type\":\"object\",\"items\":{\"$ref\":\"(make-toast)input\"}}}";
                 assertEquals(testString, inputTop.getJSONObject("properties").toString());
                 final JSONObject input = models.getJSONObject("(make-toast)input");
                 final JSONObject properties = input.getJSONObject("properties");
-                assertTrue(properties.has("toasterDoneness"));
-                assertTrue(properties.has("toasterToastType"));
+                assertTrue(properties.has("toaster:toasterDoneness"));
+                assertTrue(properties.has("toaster:toasterToastType"));
             }
         }
     }
@@ -287,10 +285,10 @@ public class ApiDocGeneratorTest {
      * @throws Exception
      */
     private void validateToaster(final ApiDeclaration doc) throws Exception {
-        final Set<String> expectedUrls = new TreeSet<>(Arrays.asList(new String[] { "/config/toaster2:toaster/",
-                "/operational/toaster2:toaster/", "/operations/toaster2:cancel-toast",
+        final Set<String> expectedUrls = new TreeSet<>(Arrays.asList(new String[]{"/config/toaster2:toaster",
+                "/operational/toaster2:toaster", "/operations/toaster2:cancel-toast",
                 "/operations/toaster2:make-toast", "/operations/toaster2:restock-toaster",
-                "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo/" }));
+                "/config/toaster2:toaster/toasterSlot/{slotId}/toaster-augmented:slotInfo"}));
 
         final Set<String> actualUrls = new TreeSet<>();
 
@@ -360,12 +358,12 @@ public class ApiDocGeneratorTest {
             final JSONObject configToaster = topLevelJson.getJSONObject("toaster2(config)toaster");
             assertNotNull("(config)toaster JSON object missing", configToaster);
             // without module prefix
-            containsProperties(configToaster, "toasterSlot");
+            containsProperties(configToaster, "toaster2:toasterSlot");
 
             final JSONObject toasterSlot = topLevelJson.getJSONObject("toaster2/toaster(config)toasterSlot");
             assertNotNull("(config)toasterSlot JSON object missing", toasterSlot);
             // with module prefix
-            containsProperties(toasterSlot, "toaster-augmented:slotInfo");
+            containsProperties(toasterSlot, "toaster2:toaster-augmented:slotInfo");
 
         } catch (final JSONException e) {
             fail("Json exception while reading JSON object. Original message " + e.getMessage());
@@ -380,4 +378,4 @@ public class ApiDocGeneratorTest {
             assertNotNull(property + " is missing", concretePropertyObject);
         }
     }
-}
+}
\ No newline at end of file
index 6342d1a780e00eed410c0803e40ce80e914b0fb7..b04064577465679862e4436bcac3c9302bcf5b7e 100644 (file)
@@ -41,7 +41,7 @@ public class MountPointSwaggerTest {
             .node(QName.create("nodes"))
             .node(QName.create("node"))
             .nodeWithKey(QName.create("node"), QName.create("id"), "123").build();
-    private static final String INSTANCE_URL = "nodes/node/123/";
+    private static final String INSTANCE_URL = "/nodes/node/123/";
     private MountPointSwagger swagger;
     private DocGenTestHelper helper;
     private SchemaContext schemaContext;
@@ -99,6 +99,7 @@ public class MountPointSwaggerTest {
         final UriInfo mockInfo = setUpSwaggerForDocGeneration();
         this.swagger.onMountPointCreated(instanceId); // add this ID into the list of
                                                  // mount points
+
         final ApiDeclaration mountPointApi = this.swagger.getMountPointApi(mockInfo, 1L, "Datastores", "-");
         assertNotNull("failed to find Datastore API", mountPointApi);
         final List<Api> apis = mountPointApi.getApis();
@@ -115,9 +116,9 @@ public class MountPointSwaggerTest {
                     .getNotes());
         }
         final Set<String> expectedApis = new TreeSet<>(Arrays.asList(new String[] {
-                "/config/" + INSTANCE_URL + "yang-ext:mount/",
-                "/operational/" + INSTANCE_URL + "yang-ext:mount/",
-                "/operations/" + INSTANCE_URL + "yang-ext:mount/", }));
+                "/config" + INSTANCE_URL + "yang-ext:mount",
+                "/operational" + INSTANCE_URL + "yang-ext:mount",
+                "/operations" + INSTANCE_URL + "yang-ext:mount",}));
         assertEquals(expectedApis, actualApis);
     }