You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by an...@apache.org on 2016/03/28 10:47:49 UTC

[09/50] [abbrv] ignite git commit: IGNITE-2597 Refactored to websockets.

IGNITE-2597 Refactored to websockets.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/706317f3
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/706317f3
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/706317f3

Branch: refs/heads/ignite-2875
Commit: 706317f3d12989883a1c199797a74a49670f94aa
Parents: 2cf28fe
Author: Andrey <an...@gridgain.com>
Authored: Fri Mar 18 15:48:01 2016 +0700
Committer: Andrey <an...@gridgain.com>
Committed: Fri Mar 18 15:48:01 2016 +0700

----------------------------------------------------------------------
 .../assembly/release-control-center-agent.xml   |   7 -
 modules/control-center-agent/pom.xml            |  45 +-
 .../console/agent/AgentConfiguration.java       |  31 +-
 .../ignite/console/agent/AgentLauncher.java     | 235 ++++++++--
 .../ignite/console/agent/AgentSocket.java       | 190 --------
 .../apache/ignite/console/agent/AgentUtils.java |   8 +-
 .../console/agent/handlers/AbstractHandler.java | 102 +++++
 .../console/agent/handlers/DatabaseHandler.java | 298 +++++++++++++
 .../handlers/DatabaseMetadataExtractor.java     | 205 ---------
 .../console/agent/handlers/RestExecutor.java    | 225 ----------
 .../console/agent/handlers/RestHandler.java     | 274 ++++++++++++
 .../ignite/console/agent/remote/Remote.java     |  37 --
 .../console/agent/remote/RemoteHandler.java     | 246 -----------
 .../console/agent/remote/WebSocketSender.java   |  39 --
 .../ignite/console/demo/AgentMetadataDemo.java  |  24 +-
 .../ignite/console/demo/AgentSqlDemo.java       | 101 +++--
 .../control-center-web/src/main/js/.eslintrc    |   1 +
 .../control-center-web/src/main/js/.gitignore   |   5 +-
 .../src/main/js/app/data/colors.json            |  22 +
 .../src/main/js/app/decorator/select.js         |  55 ++-
 .../control-center-web/src/main/js/app/index.js |  37 +-
 .../js/app/services/AgentMonitor.service.js     | 286 ++++++++++++
 .../main/js/app/services/ChartColors.service.js |  22 +
 .../main/js/app/services/Countries.service.js   |  23 +
 .../app/services/Countries/Countries.service.js |  23 -
 .../src/main/js/app/services/cleanup.service.js |  62 +++
 .../js/app/services/cleanup/cleanup.service.js  |  62 ---
 .../control-center-web/src/main/js/config.js    |  22 +-
 .../src/main/js/controllers/common-module.js    | 201 +--------
 .../main/js/controllers/domains-controller.js   | 146 +++----
 .../src/main/js/controllers/sql-controller.js   | 434 ++++++++++---------
 .../src/main/js/gulpfile.js/tasks/bundle.js     |  12 +-
 .../main/js/gulpfile.js/tasks/inject-plugins.js |   3 +-
 .../control-center-web/src/main/js/package.json |  39 +-
 modules/control-center-web/src/main/js/serve.js |  28 +-
 .../src/main/js/serve/agent.js                  | 354 +++++++++------
 .../src/main/js/serve/agent_dists/README.txt    |   7 +
 .../control-center-web/src/main/js/serve/app.js |  17 +-
 .../src/main/js/serve/config/default.json       |  26 --
 .../main/js/serve/config/settings.json.sample   |  26 ++
 .../src/main/js/serve/configure.js              |  72 +--
 .../control-center-web/src/main/js/serve/io.js  | 242 +++++++++++
 .../src/main/js/serve/mail.js                   |   5 +-
 .../src/main/js/serve/routes/agent.js           | 307 ++-----------
 .../src/main/js/serve/routes/profile.js         |  15 +-
 .../src/main/js/serve/routes/public.js          |  64 ++-
 .../src/main/js/serve/settings.js               |  11 +-
 .../src/main/js/views/includes/controls.jade    |   2 +-
 .../main/js/views/templates/agent-download.jade |  11 +-
 .../src/main/js/views/templates/select.jade     |   4 +-
 .../src/test/js/routes/agent.js                 |  10 +-
 51 files changed, 2462 insertions(+), 2261 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/assembly/release-control-center-agent.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/assembly/release-control-center-agent.xml b/modules/control-center-agent/assembly/release-control-center-agent.xml
index 90a9415..eb7da95 100644
--- a/modules/control-center-agent/assembly/release-control-center-agent.xml
+++ b/modules/control-center-agent/assembly/release-control-center-agent.xml
@@ -28,13 +28,6 @@
 
     <fileSets>
         <fileSet>
-            <directory>${basedir}/../indexing/target/libs</directory>
-            <outputDirectory>/jdbc-drivers</outputDirectory>
-            <includes>
-                <include>**/h2-*.jar</include>
-            </includes>
-        </fileSet>
-        <fileSet>
             <directory>${basedir}</directory>
             <outputDirectory>/</outputDirectory>
             <includes>

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/pom.xml
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/pom.xml b/modules/control-center-agent/pom.xml
index 2fe8844..9bee2a2 100644
--- a/modules/control-center-agent/pom.xml
+++ b/modules/control-center-agent/pom.xml
@@ -34,26 +34,20 @@
     <version>1.5.0.final-SNAPSHOT</version>
 
     <properties>
-        <jetty.version>9.2.12.v20150709</jetty.version>
+        <maven.build.timestamp.format>yyMMddHHmmss</maven.build.timestamp.format>
     </properties>
 
     <dependencies>
         <dependency>
-            <groupId>org.apache.ignite</groupId>
-            <artifactId>ignite-schema-import-db</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.eclipse.jetty.websocket</groupId>
-            <artifactId>websocket-client</artifactId>
-            <version>${jetty.version}</version>
+            <groupId>io.socket</groupId>
+            <artifactId>socket.io-client</artifactId>
+            <version>0.7.0</version>
         </dependency>
 
         <dependency>
-            <groupId>com.google.code.gson</groupId>
-            <artifactId>gson</artifactId>
-            <version>2.4</version>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-json-org</artifactId>
+            <version>2.7.1</version>
         </dependency>
 
         <dependency>
@@ -65,7 +59,19 @@
         <dependency>
             <groupId>org.apache.httpcomponents</groupId>
             <artifactId>httpclient</artifactId>
-            <version>4.5</version>
+            <version>${httpclient.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-schema-import-db</artifactId>
+            <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.gridgain</groupId>
+                    <artifactId>ignite-shmem</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -100,6 +106,9 @@
                         <manifest>
                             <mainClass>org.apache.ignite.console.agent.AgentLauncher</mainClass>
                         </manifest>
+                        <manifestEntries>
+                            <Build-Time>${maven.build.timestamp}</Build-Time>
+                        </manifestEntries>
                     </archive>
                 </configuration>
             </plugin>
@@ -118,6 +127,14 @@
 
                         <configuration>
                             <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <filters>
+                                <filter>
+                                    <artifact>*:*</artifact>
+                                    <excludes>
+                                        <exclude>META-INF/maven/**</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
                         </configuration>
                     </execution>
                 </executions>

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
index 7a0faa9..ffd30a5 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentConfiguration.java
@@ -39,7 +39,7 @@ public class AgentConfiguration {
     public static final String DFLT_CFG_PATH = "default.properties";
 
     /** Default server URI. */
-    private static final String DFLT_SERVER_URI = "wss://localhost:3001";
+    private static final String DFLT_SERVER_URI = "http://localhost:3001";
 
     /** Default Ignite node HTTP URI. */
     private static final String DFLT_NODE_URI = "http://localhost:8080";
@@ -75,9 +75,6 @@ public class AgentConfiguration {
         "      Default value: ./jdbc-drivers")
     private String driversFolder;
 
-    /** Release date. */
-    private long relDate;
-
     /** */
     @Parameter(names = { "-h", "--help" }, help = true, description = "Print this help message")
     private Boolean help;
@@ -160,17 +157,10 @@ public class AgentConfiguration {
     }
 
     /**
-     * @return Release date.
-     */
-    public long relDate() {
-        return relDate;
-    }
-
-    /**
      * @return {@code true} If agent options usage should be printed.
      */
     public Boolean help() {
-        return help != null ? help : false;
+        return help != null ? help : Boolean.FALSE;
     }
 
     /**
@@ -202,17 +192,6 @@ public class AgentConfiguration {
 
         if (val != null)
             driversFolder(val);
-
-        val = (String)props.remove("rel-date");
-
-        if (val != null) {
-            try {
-                this.relDate = Long.parseLong(val);
-            }
-            catch (NumberFormatException ignored) {
-                // No-op.
-            }
-        }
     }
 
     /**
@@ -236,8 +215,6 @@ public class AgentConfiguration {
 
         if (driversFolder == null)
             driversFolder(cmd.driversFolder());
-
-        relDate = cmd.relDate();
     }
 
     /** {@inheritDoc} */
@@ -248,12 +225,12 @@ public class AgentConfiguration {
             sb.append("User's security token         : ");
 
             if (tok.length() > 4) {
-                sb.append(new String(new char[tok.length() - 4]).replace("\0", "*"));
+                sb.append(new String(new char[tok.length() - 4]).replace('\0', '*'));
 
                 sb.append(tok.substring(tok.length() - 4));
             }
             else
-                sb.append(new String(new char[tok.length()]).replace("\0", "*"));
+                sb.append(new String(new char[tok.length()]).replace('\0', '*'));
 
             sb.append('\n');
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
index ed08d78..cb4bd46 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java
@@ -18,18 +18,39 @@
 package org.apache.ignite.console.agent;
 
 import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import io.socket.client.Ack;
+import io.socket.client.IO;
+import io.socket.client.Socket;
+import io.socket.emitter.Emitter;
 import java.io.File;
 import java.io.IOException;
+import java.net.ConnectException;
+import java.net.SocketException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.Arrays;
-
-import com.beust.jcommander.ParameterException;
-import org.apache.ignite.console.agent.handlers.RestExecutor;
+import java.util.concurrent.CountDownLatch;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import org.apache.ignite.console.agent.handlers.DatabaseHandler;
+import org.apache.ignite.console.agent.handlers.RestHandler;
+import org.apache.ignite.internal.util.typedef.X;
 import org.apache.log4j.Logger;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.eclipse.jetty.websocket.client.WebSocketClient;
-
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import static io.socket.client.Socket.EVENT_CONNECT;
+import static io.socket.client.Socket.EVENT_CONNECTING;
+import static io.socket.client.Socket.EVENT_CONNECT_ERROR;
+import static io.socket.client.Socket.EVENT_DISCONNECT;
+import static io.socket.client.Socket.EVENT_ERROR;
+import static io.socket.client.Socket.EVENT_RECONNECTING;
 import static org.apache.ignite.console.agent.AgentConfiguration.DFLT_SERVER_PORT;
 
 /**
@@ -40,16 +61,95 @@ public class AgentLauncher {
     private static final Logger log = Logger.getLogger(AgentLauncher.class.getName());
 
     /** */
+    private static final String EVENT_NODE_REST = "node:rest";
+
+    /** */
+    private static final String EVENT_SCHEMA_IMPORT_DRIVERS = "schemaImport:drivers";
+
+    /** */
+    private static final String EVENT_SCHEMA_IMPORT_SCHEMAS = "schemaImport:schemas";
+
+    /** */
+    private static final String EVENT_SCHEMA_IMPORT_METADATA = "schemaImport:metadata";
+
+    /** */
+    private static final String EVENT_AGENT_CLOSE = "agent:close";
+
+    /** */
     private static final int RECONNECT_INTERVAL = 3000;
 
     /**
+     * Create a trust manager that trusts all certificates It is not using a particular keyStore
+     */
+    private static TrustManager[] getTrustManagers() {
+        return new TrustManager[] {
+            new X509TrustManager() {
+                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+                    return null;
+                }
+
+                public void checkClientTrusted(
+                    java.security.cert.X509Certificate[] certs, String authType) {
+                }
+
+                public void checkServerTrusted(
+                    java.security.cert.X509Certificate[] certs, String authType) {
+                }
+            }};
+    }
+
+    /**
+     * On error listener.
+     */
+    private static final Emitter.Listener onError = new Emitter.Listener() {
+        @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+        @Override public void call(Object... args) {
+            Throwable e = (Throwable)args[0];
+
+            ConnectException ce = X.cause(e, ConnectException.class);
+
+            if (ce != null)
+                log.warn(ce.getMessage());
+            else {
+                Exception ignore = X.cause(e, SSLHandshakeException.class);
+
+                if (ignore != null) {
+                    log.error("Failed to establish SSL connection to server, due to errors with SSL handshake.");
+                    log.error("Add to environment variable JVM_OPTS parameter \"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate.");
+
+                    System.exit(1);
+                }
+
+                ignore = X.cause(e, SocketException.class);
+
+                if (ignore != null) {
+                    log.error("Failed to receive response from server (connection refused).");
+
+                    return;
+                }
+
+                log.error("Connection error.", e);
+            }
+        }
+    };
+
+    /**
+     * On disconnect listener.
+     */
+    private static final Emitter.Listener onDisconnect = new Emitter.Listener() {
+        @Override public void call(Object... args) {
+            log.error(String.format("Connection closed: %s.", args[0]));
+        }
+    };
+
+    /**
      * @param args Args.
      */
     @SuppressWarnings("BusyWait")
     public static void main(String[] args) throws Exception {
         log.info("Starting Apache Ignite Web Console Agent...");
 
-        AgentConfiguration cfg = new AgentConfiguration();
+        final AgentConfiguration cfg = new AgentConfiguration();
 
         JCommander jCommander = new JCommander(cfg);
 
@@ -76,13 +176,13 @@ public class AgentLauncher {
             File f = AgentUtils.resolvePath(prop);
 
             if (f == null)
-                log.warn("Failed to find agent property file: '" + prop + "'");
+                log.warn("Failed to find agent property file: " + prop);
             else
                 propCfg.load(f.toURI().toURL());
         }
         catch (IOException ignore) {
             if (!AgentConfiguration.DFLT_CFG_PATH.equals(prop))
-                log.warn("Failed to load agent property file: '" + prop + "'", ignore);
+                log.warn("Failed to load agent property file: " + prop, ignore);
         }
 
         cfg.merge(propCfg);
@@ -118,47 +218,120 @@ public class AgentLauncher {
             cfg.token(System.console().readLine().trim());
         }
 
-        RestExecutor restExecutor = new RestExecutor(cfg);
-
-        restExecutor.start();
+        final RestHandler restHnd = new RestHandler(cfg);
 
         try {
-            SslContextFactory sslCtxFactory = new SslContextFactory();
+            restHnd.start();
+
+            URI uri = URI.create(cfg.serverUri());
 
-            // Workaround for use self-signed certificate:
-            if (Boolean.getBoolean("trust.all"))
-                sslCtxFactory.setTrustAll(true);
+            if (uri.getPort() == -1)
+                uri = URI.create(cfg.serverUri() + ':' + DFLT_SERVER_PORT);
 
-            WebSocketClient client = new WebSocketClient(sslCtxFactory);
+            IO.Options opts = new IO.Options();
 
-            client.setMaxIdleTimeout(Long.MAX_VALUE);
+            opts.reconnectionDelay = RECONNECT_INTERVAL;
+
+            // Workaround for use self-signed certificate
+            if (Boolean.getBoolean("trust.all")) {
+                SSLContext ctx = SSLContext.getInstance("TLS");
+
+                // Create an SSLContext that uses our TrustManager
+                ctx.init(null, getTrustManagers(), null);
+
+                opts.sslContext = ctx;
+            }
 
-            client.start();
+            final Socket client = IO.socket(uri, opts);
 
             try {
-                while (!Thread.interrupted()) {
-                    AgentSocket agentSock = new AgentSocket(cfg, restExecutor);
+                Emitter.Listener onConnecting = new Emitter.Listener() {
+                    @Override public void call(Object... args) {
+                        log.info("Connecting to: " + cfg.serverUri());
+                    }
+                };
 
-                    log.info("Connecting to: " + cfg.serverUri());
+                Emitter.Listener onConnect = new Emitter.Listener() {
+                    @Override public void call(Object... args) {
+                        log.info("Connection established.");
 
-                    URI uri = URI.create(cfg.serverUri());
+                        JSONObject authMsg = new JSONObject();
 
-                    if (uri.getPort() == -1)
-                        uri = URI.create(cfg.serverUri() + ":" + DFLT_SERVER_PORT);
+                        try {
+                            authMsg.put("token", cfg.token());
 
-                    client.connect(agentSock, uri);
+                            String clsName = AgentLauncher.class.getSimpleName() + ".class";
 
-                    agentSock.waitForClose();
+                            String clsPath = AgentLauncher.class.getResource(clsName).toString();
 
-                    Thread.sleep(RECONNECT_INTERVAL);
-                }
+                            if (clsPath.startsWith("jar")) {
+                                String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) +
+                                    "/META-INF/MANIFEST.MF";
+
+                                Manifest manifest = new Manifest(new URL(manifestPath).openStream());
+
+                                Attributes attr = manifest.getMainAttributes();
+
+                                authMsg.put("ver", attr.getValue("Implementation-Version"));
+                                authMsg.put("bt", attr.getValue("Build-Time"));
+                            }
+
+                            client.emit("agent:auth", authMsg, new Ack() {
+                                @Override public void call(Object... args) {
+                                    // Authentication failed if response contains args.
+                                    if (args != null && args.length > 0) {
+                                        onDisconnect.call("Authentication failed: " + args[0]);
+
+                                        System.exit(1);
+                                    }
+
+                                    log.info("Authentication success.");
+                                }
+                            });
+                        }
+                        catch (JSONException | IOException e) {
+                            log.error("Failed to construct authentication message", e);
+
+                            client.close();
+                        }
+                    }
+                };
+
+                DatabaseHandler dbHnd = new DatabaseHandler(cfg);
+
+                final CountDownLatch latch = new CountDownLatch(1);
+
+                client
+                    .on(EVENT_CONNECTING, onConnecting)
+                    .on(EVENT_CONNECT, onConnect)
+                    .on(EVENT_CONNECT_ERROR, onError)
+                    .on(EVENT_RECONNECTING, onConnecting)
+                    .on(EVENT_NODE_REST, restHnd)
+                    .on(EVENT_SCHEMA_IMPORT_DRIVERS, dbHnd.availableDriversListener())
+                    .on(EVENT_SCHEMA_IMPORT_SCHEMAS, dbHnd.schemasListener())
+                    .on(EVENT_SCHEMA_IMPORT_METADATA, dbHnd.metadataListener())
+                    .on(EVENT_ERROR, onError)
+                    .on(EVENT_DISCONNECT, onDisconnect)
+                    .on(EVENT_AGENT_CLOSE, new Emitter.Listener() {
+                        @Override public void call(Object... args) {
+                            onDisconnect.call(args);
+
+                            client.off();
+
+                            latch.countDown();
+                        }
+                    });
+
+                client.connect();
+
+                latch.await();
             }
             finally {
-                client.stop();
+                client.close();
             }
         }
         finally {
-            restExecutor.stop();
+            restHnd.stop();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentSocket.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentSocket.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentSocket.java
deleted file mode 100644
index ae6da07..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentSocket.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import java.io.IOException;
-import java.net.ConnectException;
-import java.util.concurrent.CountDownLatch;
-import javax.net.ssl.SSLHandshakeException;
-import org.apache.ignite.console.agent.handlers.DatabaseMetadataExtractor;
-import org.apache.ignite.console.agent.handlers.RestExecutor;
-import org.apache.ignite.console.agent.remote.Remote;
-import org.apache.ignite.console.agent.remote.RemoteHandler;
-import org.apache.ignite.console.agent.remote.WebSocketSender;
-import org.apache.log4j.Logger;
-import org.eclipse.jetty.websocket.api.Session;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
-import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
-import org.eclipse.jetty.websocket.api.annotations.WebSocket;
-
-/**
- * Handler for web-socket connection.
- */
-@WebSocket
-public class AgentSocket implements WebSocketSender {
-    /** */
-    public static final Gson GSON = new Gson();
-
-    /** */
-    public static final JsonParser PARSER = new JsonParser();
-
-    /** */
-    private static final Logger log = Logger.getLogger(AgentSocket.class.getName());
-
-    /** */
-    private final CountDownLatch closeLatch = new CountDownLatch(1);
-
-    /** */
-    private final AgentConfiguration cfg;
-
-    /** */
-    private final RestExecutor restExecutor;
-
-    /** */
-    private RemoteHandler remote;
-
-    /** */
-    private Session ses;
-
-    /**
-     * @param cfg Config.
-     */
-    public AgentSocket(AgentConfiguration cfg, RestExecutor restExecutor) {
-        this.cfg = cfg;
-        this.restExecutor = restExecutor;
-    }
-
-    /**
-     * @param statusCode Status code.
-     * @param reason Reason.
-     */
-    @OnWebSocketClose
-    public void onClose(int statusCode, String reason) {
-        log.error(String.format("Connection closed: %d - %s.", statusCode, reason));
-
-        if (remote != null)
-            remote.close();
-
-        closeLatch.countDown();
-    }
-
-    /**
-     * @param ses Session.
-     */
-    @OnWebSocketConnect
-    public void onConnect(Session ses) {
-        log.info("Connection established.");
-
-        this.ses = ses;
-
-        remote = RemoteHandler.wrap(this, this, restExecutor, new DatabaseMetadataExtractor(cfg));
-
-        JsonObject authMsg = new JsonObject();
-
-        authMsg.addProperty("type", "AuthMessage");
-        authMsg.addProperty("token", cfg.token());
-        authMsg.addProperty("relDate", cfg.relDate());
-
-        send(authMsg);
-    }
-
-    /**
-     * @param msg Message.
-     * @return Whether or not message was sent.
-     */
-    @Override public boolean send(JsonObject msg) {
-        return send(GSON.toJson(msg));
-    }
-
-    /**
-     * @param msg Message.
-     * @return Whether or not message was sent.
-     */
-    @Override public boolean send(String msg) {
-        try {
-            ses.getRemote().sendString(msg);
-
-            return true;
-        }
-        catch (IOException ignored) {
-            log.error("Failed to send message to Control Center.");
-
-            return false;
-        }
-    }
-
-    /**
-     * @param ses Session.
-     * @param error Error.
-     */
-    @OnWebSocketError
-    public void onError(Session ses, Throwable error) {
-        if (error instanceof ConnectException)
-            log.warn(error.getMessage());
-        else if (error instanceof SSLHandshakeException) {
-            log.error("Failed to establish SSL connection to Ignite Console. Start agent with " +
-                "\"-Dtrust.all=true\" to skip certificate validation in case of using self-signed certificate.", error);
-
-            System.exit(1);
-        }
-        else
-            log.error("Connection error.", error);
-
-        if (remote != null)
-            remote.close();
-
-        closeLatch.countDown();
-    }
-
-    /**
-     * @param msg Message.
-     */
-    @OnWebSocketMessage
-    public void onMessage(String msg) {
-        JsonElement jsonElement = PARSER.parse(msg);
-
-        remote.onMessage((JsonObject)jsonElement);
-    }
-
-    /**
-     * @param errorMsg Authentication failed message or {@code null} if authentication success.
-     */
-    @Remote
-    public void authResult(String errorMsg) {
-        if (errorMsg != null) {
-            onClose(401, "Authentication failed: " + errorMsg);
-
-            System.exit(1);
-        }
-
-        log.info("Authentication success.");
-    }
-
-    /**
-     * Await socket close.
-     */
-    public void waitForClose() throws InterruptedException {
-        closeLatch.await();
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
index 349eea1..50a849a 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java
@@ -17,12 +17,11 @@
 
 package org.apache.ignite.console.agent;
 
-import org.apache.log4j.Logger;
-
 import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.ProtectionDomain;
+import org.apache.log4j.Logger;
 
 /**
  * Utility methods.
@@ -101,10 +100,7 @@ public class AgentUtils {
                 return file;
         }
 
-        /*
-         * 2. Check given path as absolute.
-         */
-
+        // 2. Check given path as absolute.
         File file = new File(path);
 
         if (file.exists())

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java
new file mode 100644
index 0000000..aa38dd7
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java
@@ -0,0 +1,102 @@
+/* @java.file.header */
+
+/*  _________        _____ __________________        _____
+ *  __  ____/___________(_)______  /__  ____/______ ____(_)_______
+ *  _  / __  __  ___/__  / _  __  / _  / __  _  __ `/__  / __  __ \
+ *  / /_/ /  _  /    _  /  / /_/ /  / /_/ /  / /_/ / _  /  _  / / /
+ *  \____/   /_/     /_/   \_,__/   \____/   \__,_/  /_/   /_/ /_/
+ */
+
+package org.apache.ignite.console.agent.handlers;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
+import io.socket.client.Ack;
+import io.socket.emitter.Emitter;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+/**
+ * Base class for web socket handlers.
+ */
+abstract class AbstractHandler implements Emitter.Listener {
+    /** JSON object mapper. */
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    static {
+        JsonOrgModule module = new JsonOrgModule();
+
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
+        mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
+
+        mapper.registerModule(module);
+    }
+
+    /**
+     * @param obj Object.
+     * @return {@link JSONObject} or {@link JSONArray}.
+     */
+    private Object toJSON(Object obj) {
+        if (obj instanceof Iterable)
+            return mapper.convertValue(obj, JSONArray.class);
+
+        return mapper.convertValue(obj, JSONObject.class);
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public final void call(Object... args) {
+        Ack cb = null;
+
+        try {
+            if (args == null || args.length == 0)
+                throw new IllegalArgumentException("Missing arguments.");
+
+            if (args.length > 2)
+                throw new IllegalArgumentException("Wrong arguments count, must be <= 2: " + Arrays.toString(args));
+
+            JSONObject lsnrArgs = null;
+
+            if (args.length == 1) {
+                if (args[0] instanceof JSONObject)
+                    lsnrArgs = (JSONObject)args[0];
+                else if (args[0] instanceof Ack)
+                    cb = (Ack)args[0];
+                else
+                    throw new IllegalArgumentException("Wrong type of argument, must be JSONObject or Ack: " + args[0]);
+            }
+            else {
+                if (args[0] != null && !(args[0] instanceof JSONObject))
+                    throw new IllegalArgumentException("Wrong type of argument, must be JSONObject: " + args[0]);
+
+                if (!(args[1] instanceof Ack))
+                    throw new IllegalArgumentException("Wrong type of argument, must be Ack: " + args[1]);
+
+                lsnrArgs = (JSONObject)args[0];
+
+                cb = (Ack)args[1];
+            }
+
+            Object res = execute(lsnrArgs == null ? Collections.emptyMap() : mapper.convertValue(lsnrArgs, Map.class));
+
+            if (cb != null)
+                cb.call(null, toJSON(res));
+        }
+        catch (Exception e) {
+            if (cb != null)
+                cb.call(e, null);
+        }
+    }
+
+    /**
+     * Execute command with specified arguments.
+     *
+     * @param args Map with method args.
+     */
+    public abstract Object execute(Map<String, Object> args) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java
new file mode 100644
index 0000000..02146d9
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.console.agent.handlers;
+
+import io.socket.emitter.Emitter;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import org.apache.ignite.console.agent.AgentConfiguration;
+import org.apache.ignite.console.demo.AgentMetadataDemo;
+import org.apache.ignite.schema.parser.DbMetadataReader;
+import org.apache.ignite.schema.parser.DbTable;
+import org.apache.log4j.Logger;
+
+import static org.apache.ignite.console.agent.AgentUtils.resolvePath;
+
+/**
+ * API to extract database metadata.
+ */
+public class DatabaseHandler {
+    /** */
+    private static final Logger log = Logger.getLogger(DatabaseHandler.class.getName());
+
+    /** */
+    private final File driversFolder;
+
+    /**
+     * @param cfg Config.
+     */
+    public DatabaseHandler(AgentConfiguration cfg) {
+        driversFolder = resolvePath(cfg.driversFolder() == null ? "jdbc-drivers" : cfg.driversFolder());
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @return Connection to database.
+     * @throws SQLException
+     */
+    private Connection connect(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
+        Properties jdbcInfo) throws SQLException {
+        if (AgentMetadataDemo.isTestDriveUrl(jdbcUrl))
+            return AgentMetadataDemo.testDrive();
+
+        if (!new File(jdbcDriverJarPath).isAbsolute() && driversFolder != null)
+            jdbcDriverJarPath = new File(driversFolder, jdbcDriverJarPath).getPath();
+
+        return DbMetadataReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo);
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @return Collection of schema names.
+     * @throws SQLException
+     */
+    protected Collection<String> schemas(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
+        Properties jdbcInfo) throws SQLException {
+        if (log.isDebugEnabled())
+            log.debug("Start collecting database schemas [drvJar=" + jdbcDriverJarPath +
+                ", drvCls=" + jdbcDriverCls + ", jdbcUrl=" + jdbcUrl + "]");
+
+        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
+            Collection<String> schemas = DbMetadataReader.getInstance().schemas(conn);
+
+            if (log.isDebugEnabled())
+                log.debug("Finished collection of schemas [jdbcUrl=" + jdbcUrl + ", count=" + schemas.size() + "]");
+
+            return schemas;
+        }
+        catch (Throwable e) {
+            log.error("Failed to collect schemas", e);
+
+            throw new SQLException("Failed to collect schemas", e);
+        }
+    }
+
+    /**
+     * Listener for schema names.
+     *
+     * @return Collection of schema names.
+     */
+    public Emitter.Listener schemasListener() {
+        return new AbstractHandler() {
+            @Override public Object execute(Map<String, Object> args) throws Exception {
+                String driverPath = null;
+
+                if (args.containsKey("driverPath"))
+                    driverPath = args.get("driverPath").toString();
+
+                if (!args.containsKey("driverClass"))
+                    throw new IllegalArgumentException("Missing driverClass in arguments: " + args);
+
+                String driverCls = args.get("driverClass").toString();
+
+                if (!args.containsKey("url"))
+                    throw new IllegalArgumentException("Missing url in arguments: " + args);
+
+                String url = args.get("url").toString();
+
+                if (!args.containsKey("info"))
+                    throw new IllegalArgumentException("Missing info in arguments: " + args);
+
+                Properties info = new Properties();
+
+                info.putAll((Map)args.get("info"));
+
+                return schemas(driverPath, driverCls, url, info);
+            }
+        };
+    }
+
+    /**
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverCls JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     * @param schemas List of schema names to process.
+     * @param tblsOnly If {@code true} then only tables will be processed otherwise views also will be processed.
+     * @return Collection of tables.
+     */
+    protected Collection<DbTable> metadata(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
+        Properties jdbcInfo, List<String> schemas, boolean tblsOnly) throws SQLException {
+        if (log.isDebugEnabled())
+            log.debug("Start collecting database metadata [drvJar=" + jdbcDriverJarPath +
+                ", drvCls=" + jdbcDriverCls + ", jdbcUrl=" + jdbcUrl + "]");
+
+        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
+            Collection<DbTable> metadata = DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly);
+
+            if (log.isDebugEnabled())
+                log.debug("Finished collection of metadata [jdbcUrl=" + jdbcUrl + ", count=" + metadata.size() + "]");
+
+            return metadata;
+        }
+        catch (Throwable e) {
+            log.error("Failed to collect metadata", e);
+
+            throw new SQLException("Failed to collect metadata", e);
+        }
+    }
+
+    /**
+     * Listener for tables.
+     *
+     * @return Collection of tables.
+     */
+    public Emitter.Listener metadataListener() {
+        return new AbstractHandler() {
+            @SuppressWarnings("unchecked")
+            @Override public Object execute(Map<String, Object> args) throws Exception {
+                String driverPath = null;
+
+                if (args.containsKey("driverPath"))
+                    driverPath = args.get("driverPath").toString();
+
+                if (!args.containsKey("driverClass"))
+                    throw new IllegalArgumentException("Missing driverClass in arguments: " + args);
+
+                String driverCls = args.get("driverClass").toString();
+
+                if (!args.containsKey("url"))
+                    throw new IllegalArgumentException("Missing url in arguments: " + args);
+
+                String url = args.get("url").toString();
+
+                if (!args.containsKey("info"))
+                    throw new IllegalArgumentException("Missing info in arguments: " + args);
+
+                Properties info = new Properties();
+
+                info.putAll((Map)args.get("info"));
+
+                if (!args.containsKey("schemas"))
+                    throw new IllegalArgumentException("Missing schemas in arguments: " + args);
+
+                List<String> schemas = (List<String>)args.get("schemas");
+
+                if (!args.containsKey("tablesOnly"))
+                    throw new IllegalArgumentException("Missing tablesOnly in arguments: " + args);
+
+                boolean tblsOnly = (boolean)args.get("tablesOnly");
+
+                return metadata(driverPath, driverCls, url, info, schemas, tblsOnly);
+            }
+        };
+    }
+
+    /**
+     * Listener for drivers.
+     *
+     * @return Drivers in drivers folder
+     * @see AgentConfiguration#driversFolder
+     */
+    public Emitter.Listener availableDriversListener() {
+        return new AbstractHandler() {
+            @Override public Object execute(Map<String, Object> args) throws Exception {
+                if (driversFolder == null) {
+                    log.info("JDBC drivers folder not specified, returning empty list");
+
+                    return Collections.emptyList();
+                }
+
+                if (log.isDebugEnabled())
+                    log.debug("Collecting JDBC drivers in folder: " + driversFolder.getPath());
+
+                File[] list = driversFolder.listFiles(new FilenameFilter() {
+                    @Override public boolean accept(File dir, String name) {
+                        return name.endsWith(".jar");
+                    }
+                });
+
+                if (list == null) {
+                    log.info("JDBC drivers folder has no files, returning empty list");
+
+                    return Collections.emptyList();
+                }
+
+                List<JdbcDriver> res = new ArrayList<>();
+
+                for (File file : list) {
+                    try {
+                        boolean win = System.getProperty("os.name").contains("win");
+
+                        URL url = new URL("jar", null,
+                            "file:" + (win ? "/" : "") + file.getPath() + "!/META-INF/services/java.sql.Driver");
+
+                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
+                            String jdbcDriverCls = reader.readLine();
+
+                            res.add(new JdbcDriver(file.getName(), jdbcDriverCls));
+
+                            if (log.isDebugEnabled())
+                                log.debug("Found: [driver=" + file + ", class=" + jdbcDriverCls + "]");
+                        }
+                    }
+                    catch (IOException e) {
+                        res.add(new JdbcDriver(file.getName(), null));
+
+                        log.info("Found: [driver=" + file + "]");
+                        log.info("Failed to detect driver class: " + e.getMessage());
+                    }
+                }
+
+                return res;
+            }
+        };
+    }
+
+    /**
+     * Wrapper class for later to be transformed to JSON and send to Web Console.
+     */
+    private static class JdbcDriver {
+        /** */
+        public final String jdbcDriverJar;
+        /** */
+        public final String jdbcDriverCls;
+
+        /**
+         * @param jdbcDriverJar File name of driver jar file.
+         * @param jdbcDriverCls Optional JDBC driver class.
+         */
+        public JdbcDriver(String jdbcDriverJar, String jdbcDriverCls) {
+            this.jdbcDriverJar = jdbcDriverJar;
+            this.jdbcDriverCls = jdbcDriverCls;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseMetadataExtractor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseMetadataExtractor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseMetadataExtractor.java
deleted file mode 100644
index 48d5236..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseMetadataExtractor.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent.handlers;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FilenameFilter;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Properties;
-import org.apache.ignite.console.agent.AgentConfiguration;
-import org.apache.ignite.console.agent.remote.Remote;
-import org.apache.ignite.console.demo.AgentMetadataDemo;
-import org.apache.ignite.schema.parser.DbMetadataReader;
-import org.apache.ignite.schema.parser.DbTable;
-import org.apache.log4j.Logger;
-
-import static org.apache.ignite.console.agent.AgentUtils.resolvePath;
-
-/**
- * Remote API to extract database metadata.
- */
-public class DatabaseMetadataExtractor {
-    /** */
-    private static final Logger log = Logger.getLogger(DatabaseMetadataExtractor.class.getName());
-
-    /** */
-    private final File driversFolder;
-
-    /**
-     * @param cfg Config.
-     */
-    public DatabaseMetadataExtractor(AgentConfiguration cfg) {
-        driversFolder = resolvePath(cfg.driversFolder() == null ? "jdbc-drivers" : cfg.driversFolder());
-    }
-
-    /**
-     * @param jdbcDriverJarPath JDBC driver JAR path.
-     * @param jdbcDriverCls JDBC driver class.
-     * @param jdbcUrl JDBC URL.
-     * @param jdbcInfo Properties to connect to database.
-     * @return Connection to database.
-     * @throws SQLException
-     */
-    private Connection connect(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl, Properties jdbcInfo) throws SQLException {
-        if (!new File(jdbcDriverJarPath).isAbsolute() && driversFolder != null)
-            jdbcDriverJarPath = new File(driversFolder, jdbcDriverJarPath).getPath();
-
-        if (AgentMetadataDemo.isTestDriveUrl(jdbcUrl))
-            AgentMetadataDemo.testDrive();
-
-        return DbMetadataReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo);
-    }
-
-    /**
-     * @param jdbcDriverJarPath JDBC driver JAR path.
-     * @param jdbcDriverCls JDBC driver class.
-     * @param jdbcUrl JDBC URL.
-     * @param jdbcInfo Properties to connect to database.
-     * @return Collection of schema names.
-     * @throws SQLException
-     */
-    @Remote
-    public Collection<String> schemas(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
-        Properties jdbcInfo) throws SQLException {
-        log.debug("Start collecting database schemas [driver jar=" + jdbcDriverJarPath +
-            ", driver class=" + jdbcDriverCls + ", url=" + jdbcUrl + "]");
-
-        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
-            Collection<String> schemas = DbMetadataReader.getInstance().schemas(conn);
-
-            log.debug("Finished collection of schemas [url=" + jdbcUrl + ", count="+ schemas.size() +"]");
-
-            return schemas;
-        }
-        catch (Throwable e) {
-            log.error("Failed to collect schemas", e);
-
-            throw new SQLException("Failed to collect schemas", e);
-        }
-    }
-
-    /**
-     * @param jdbcDriverJarPath JDBC driver JAR path.
-     * @param jdbcDriverCls JDBC driver class.
-     * @param jdbcUrl JDBC URL.
-     * @param jdbcInfo Properties to connect to database.
-     * @param schemas List of schema names to process.
-     * @param tblsOnly If {@code true} then only tables will be processed otherwise views also will be processed.
-     * @return Collection of tables.
-     */
-    @Remote
-    public Collection<DbTable> metadata(String jdbcDriverJarPath, String jdbcDriverCls, String jdbcUrl,
-        Properties jdbcInfo, List<String> schemas, boolean tblsOnly) throws SQLException {
-        log.debug("Start collecting database metadata [driver jar=" + jdbcDriverJarPath +
-            ", driver class=" + jdbcDriverCls + ", url=" + jdbcUrl + "]");
-
-        try (Connection conn = connect(jdbcDriverJarPath, jdbcDriverCls, jdbcUrl, jdbcInfo)) {
-            Collection<DbTable> metadata = DbMetadataReader.getInstance().metadata(conn, schemas, tblsOnly);
-
-            log.debug("Finished collection of metadata [url=" + jdbcUrl + ", count="+ metadata.size() +"]");
-
-            return metadata;
-        }
-        catch (Throwable e) {
-            log.error("Failed to collect metadata", e);
-
-            throw new SQLException("Failed to collect metadata", e);
-        }
-    }
-
-    /**
-     * @return Drivers in drivers folder
-     * @see AgentConfiguration#driversFolder
-     */
-    @Remote
-    public List<JdbcDriver> availableDrivers() {
-        if (driversFolder == null) {
-            log.info("JDBC drivers folder not specified, returning empty list");
-
-            return Collections.emptyList();
-        }
-
-        log.debug("Collecting JDBC drivers in folder: " + driversFolder.getPath());
-
-        File[] list = driversFolder.listFiles(new FilenameFilter() {
-            @Override public boolean accept(File dir, String name) {
-                return name.endsWith(".jar");
-            }
-        });
-
-        if (list == null) {
-            log.info("JDBC drivers folder has no files, returning empty list");
-
-            return Collections.emptyList();
-        }
-
-        List<JdbcDriver> res = new ArrayList<>();
-
-        for (File file : list) {
-            try {
-                boolean win = System.getProperty("os.name").contains("win");
-
-                URL url = new URL("jar", null, "file:" + (win ? "/" : "") + file.getPath() + "!/META-INF/services/java.sql.Driver");
-
-                try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
-                    String jdbcDriverCls = reader.readLine();
-
-                    res.add(new JdbcDriver(file.getName(), jdbcDriverCls));
-
-                    log.debug("Found: [driver=" + file + ", class=" + jdbcDriverCls + "]");
-                }
-            }
-            catch (IOException e) {
-                res.add(new JdbcDriver(file.getName(), null));
-
-                log.info("Found: [driver=" + file + "]");
-                log.info("Failed to detect driver class: " + e.getMessage());
-            }
-        }
-
-        return res;
-    }
-
-    /**
-     * Wrapper class for later to be transformed to JSON and send to Web Console.
-     */
-    private static class JdbcDriver {
-        /** */
-        private final String jdbcDriverJar;
-        /** */
-        private final String jdbcDriverClass;
-
-        /**
-         * @param jdbcDriverJar File name of driver jar file.
-         * @param jdbcDriverClass Optional JDBC driver class.
-         */
-        public JdbcDriver(String jdbcDriverJar, String jdbcDriverClass) {
-            this.jdbcDriverJar = jdbcDriverJar;
-            this.jdbcDriverClass = jdbcDriverClass;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestExecutor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestExecutor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestExecutor.java
deleted file mode 100644
index 3eb869b..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestExecutor.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent.handlers;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.ConnectException;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.util.List;
-import java.util.Map;
-import org.apache.commons.codec.Charsets;
-import org.apache.http.Header;
-import org.apache.http.NameValuePair;
-import org.apache.http.client.entity.UrlEncodedFormEntity;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpRequestBase;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.entity.StringEntity;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.apache.ignite.console.agent.AgentConfiguration;
-import org.apache.ignite.console.agent.remote.Remote;
-import org.apache.ignite.console.demo.AgentSqlDemo;
-import org.apache.log4j.Logger;
-
-import static org.apache.ignite.console.agent.AgentConfiguration.DFLT_NODE_PORT;
-
-/**
- * Executor for REST requests.
- */
-public class RestExecutor {
-    /** */
-    private static final Logger log = Logger.getLogger(RestExecutor.class.getName());
-
-    /** */
-    private final AgentConfiguration cfg;
-
-    /** */
-    private CloseableHttpClient httpClient;
-
-    /**
-     * @param cfg Config.
-     */
-    public RestExecutor(AgentConfiguration cfg) {
-        this.cfg = cfg;
-    }
-
-    /**
-     *
-     */
-    public void start() {
-        httpClient = HttpClientBuilder.create().build();
-    }
-
-    /**
-     *
-     */
-    public void stop() throws IOException {
-        if (httpClient != null)
-            httpClient.close();
-    }
-
-    /**
-     * @param uri Url.
-     * @param params Params.
-     * @param demo Use demo node.
-     * @param mtd Method.
-     * @param headers Headers.
-     * @param body Body.
-     */
-    @Remote
-    public RestResult executeRest(String uri, Map<String, String> params, boolean demo,
-        String mtd, Map<String, String> headers, String body) throws IOException, URISyntaxException {
-        log.debug("Start execute REST command [method=" + mtd + ", uri=/" + uri + ", parameters=" + params + "]");
-
-        final URIBuilder builder;
-
-        if (demo) {
-            // try start demo if needed.
-            AgentSqlDemo.testDrive(cfg);
-
-            // null if demo node not started yet.
-            if (cfg.demoNodeUri() == null)
-                return RestResult.fail(404, "Demo node is not started yet.");
-
-            builder = new URIBuilder(cfg.demoNodeUri());
-        }
-        else
-            builder = new URIBuilder(cfg.nodeUri());
-
-        if (builder.getPort() == -1)
-            builder.setPort(DFLT_NODE_PORT);
-
-        if (uri != null) {
-            if (!uri.startsWith("/") && !cfg.nodeUri().endsWith("/"))
-                uri = '/' + uri;
-
-            builder.setPath(uri);
-        }
-
-        if (params != null) {
-            for (Map.Entry<String, String> entry : params.entrySet())
-                builder.addParameter(entry.getKey(), entry.getValue());
-        }
-
-        HttpRequestBase httpReq;
-
-        if ("GET".equalsIgnoreCase(mtd))
-            httpReq = new HttpGet(builder.build());
-        else if ("POST".equalsIgnoreCase(mtd)) {
-            HttpPost post;
-
-            if (body == null) {
-                List<NameValuePair> nvps = builder.getQueryParams();
-
-                builder.clearParameters();
-
-                post = new HttpPost(builder.build());
-
-                if (!nvps.isEmpty())
-                    post.setEntity(new UrlEncodedFormEntity(nvps));
-            }
-            else {
-                post = new HttpPost(builder.build());
-
-                post.setEntity(new StringEntity(body));
-            }
-
-            httpReq = post;
-        }
-        else
-            throw new IOException("Unknown HTTP-method: " + mtd);
-
-        if (headers != null) {
-            for (Map.Entry<String, String> entry : headers.entrySet())
-                httpReq.addHeader(entry.getKey(), entry.getValue());
-        }
-
-        try (CloseableHttpResponse resp = httpClient.execute(httpReq)) {
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-
-            resp.getEntity().writeTo(out);
-
-            Charset charset = Charsets.UTF_8;
-
-            Header encodingHdr = resp.getEntity().getContentEncoding();
-
-            if (encodingHdr != null) {
-                String encoding = encodingHdr.getValue();
-
-                charset = Charsets.toCharset(encoding);
-            }
-
-            return RestResult.success(resp.getStatusLine().getStatusCode(), new String(out.toByteArray(), charset));
-        }
-        catch (ConnectException e) {
-            log.info("Failed connect to node and execute REST command [uri=" + builder.build() + "]");
-
-            return RestResult.fail(404, "Failed connect to node and execute REST command.");
-        }
-    }
-
-    /**
-     * Request result.
-     */
-    public static class RestResult {
-        /** REST http code. */
-        private int restCode;
-
-        /** The field contains description of error if server could not handle the request. */
-        private String error;
-
-        /** The field contains result of command. */
-        private String response;
-
-        /**
-         * @param restCode REST http code.
-         * @param error The field contains description of error if server could not handle the request.
-         * @param response The field contains result of command.
-         */
-        private RestResult(int restCode, String error, String response) {
-            this.restCode = restCode;
-            this.error = error;
-            this.response = response;
-        }
-
-        /**
-         * @param restCode REST http code.
-         * @param error The field contains description of error if server could not handle the request.
-
-         * @return Request result.
-         */
-        public static RestResult fail(int restCode, String error) {
-            return new RestResult(restCode, error, null);
-        }
-
-        /**
-         * @param restCode REST http code.
-         * @param response The field contains result of command.
-
-         * @return Request result.
-         */
-        public static RestResult success(int restCode, String response) {
-            return new RestResult(restCode, null, response);
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java
new file mode 100644
index 0000000..2f476cf
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.console.agent.handlers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.codec.Charsets;
+import org.apache.http.Header;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.ignite.console.agent.AgentConfiguration;
+import org.apache.ignite.console.demo.AgentSqlDemo;
+import org.apache.log4j.Logger;
+
+import static org.apache.ignite.console.agent.AgentConfiguration.DFLT_NODE_PORT;
+
+/**
+ * API to translate REST requests to Ignite cluster.
+ */
+public class RestHandler extends AbstractHandler {
+    /** */
+    private static final Logger log = Logger.getLogger(RestHandler.class.getName());
+
+    /** */
+    private final AgentConfiguration cfg;
+
+    /** */
+    private CloseableHttpClient httpClient;
+
+    /**
+     * @param cfg Config.
+     */
+    public RestHandler(AgentConfiguration cfg) {
+        this.cfg = cfg;
+    }
+
+    /**
+     * Start HTTP client for communication with node via REST.
+     */
+    public void start() {
+        httpClient = HttpClientBuilder.create().build();
+    }
+
+    /**
+     * Stop HTTP client.
+     */
+    public void stop() {
+        if (httpClient != null) {
+            try {
+                httpClient.close();
+            }
+            catch (IOException ignore) {
+                // No-op.
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    @Override public Object execute(Map<String, Object> args) throws Exception {
+        if (log.isDebugEnabled())
+            log.debug("Start parse REST command args: " + args);
+
+        String uri = null;
+
+        if (args.containsKey("uri"))
+            uri = args.get("uri").toString();
+
+        Map<String, Object> params = null;
+
+        if (args.containsKey("params"))
+            params = (Map<String, Object>)args.get("params");
+
+        if (!args.containsKey("demo"))
+            throw new IllegalArgumentException("Missing demo flag in arguments: " + args);
+
+        boolean demo = (boolean)args.get("demo");
+
+        if (!args.containsKey("method"))
+            throw new IllegalArgumentException("Missing method in arguments: " + args);
+
+        String mtd = args.get("method").toString();
+
+        Map<String, Object> headers = null;
+
+        if (args.containsKey("headers"))
+            headers = (Map<String, Object>)args.get("headers");
+
+        String body = null;
+
+        if (args.containsKey("body"))
+            body = args.get("body").toString();
+
+        return executeRest(uri, params, demo, mtd, headers, body);
+    }
+
+    /**
+     * @param uri Url.
+     * @param params Params.
+     * @param demo Use demo node.
+     * @param mtd Method.
+     * @param headers Headers.
+     * @param body Body.
+     */
+    protected RestResult executeRest(String uri, Map<String, Object> params, boolean demo,
+        String mtd, Map<String, Object> headers, String body) throws IOException, URISyntaxException {
+        if (log.isDebugEnabled())
+            log.debug("Start execute REST command [method=" + mtd + ", uri=/" + (uri == null ? "" : uri) +
+                ", parameters=" + params + "]");
+
+        final URIBuilder builder;
+
+        if (demo) {
+            // try start demo if needed.
+            AgentSqlDemo.testDrive(cfg);
+
+            // null if demo node not started yet.
+            if (cfg.demoNodeUri() == null)
+                return RestResult.fail("Demo node is not started yet.", 404);
+
+            builder = new URIBuilder(cfg.demoNodeUri());
+        }
+        else
+            builder = new URIBuilder(cfg.nodeUri());
+
+        if (builder.getPort() == -1)
+            builder.setPort(DFLT_NODE_PORT);
+
+        if (uri != null) {
+            if (!uri.startsWith("/") && !cfg.nodeUri().endsWith("/"))
+                uri = '/' + uri;
+
+            builder.setPath(uri);
+        }
+
+        if (params != null) {
+            for (Map.Entry<String, Object> entry : params.entrySet())
+                builder.addParameter(entry.getKey(), entry.getValue() == null ? null : entry.getValue().toString());
+        }
+
+        HttpRequestBase httpReq = null;
+
+        try {
+            if ("GET".equalsIgnoreCase(mtd))
+                httpReq = new HttpGet(builder.build());
+            else if ("POST".equalsIgnoreCase(mtd)) {
+                HttpPost post;
+
+                if (body == null) {
+                    List<NameValuePair> nvps = builder.getQueryParams();
+
+                    builder.clearParameters();
+
+                    post = new HttpPost(builder.build());
+
+                    if (!nvps.isEmpty())
+                        post.setEntity(new UrlEncodedFormEntity(nvps));
+                }
+                else {
+                    post = new HttpPost(builder.build());
+
+                    post.setEntity(new StringEntity(body));
+                }
+
+                httpReq = post;
+            }
+            else
+                throw new IOException("Unknown HTTP-method: " + mtd);
+
+            if (headers != null) {
+                for (Map.Entry<String, Object> entry : headers.entrySet())
+                    httpReq.addHeader(entry.getKey(), entry.getValue() == null ? null : entry.getValue().toString());
+            }
+
+            try (CloseableHttpResponse resp = httpClient.execute(httpReq)) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+                resp.getEntity().writeTo(out);
+
+                Charset charset = Charsets.UTF_8;
+
+                Header encodingHdr = resp.getEntity().getContentEncoding();
+
+                if (encodingHdr != null) {
+                    String encoding = encodingHdr.getValue();
+
+                    charset = Charsets.toCharset(encoding);
+                }
+
+                return RestResult.success(resp.getStatusLine().getStatusCode(), new String(out.toByteArray(), charset));
+            }
+            catch (ConnectException e) {
+                log.info("Failed connect to node and execute REST command [uri=" + builder.build() + "]");
+
+                return RestResult.fail("Failed connect to node and execute REST command.", 404);
+            }
+        }
+        finally {
+            if (httpReq != null)
+                httpReq.reset();
+        }
+    }
+
+    /**
+     * Request result.
+     */
+    public static class RestResult {
+        /** The field contains description of error if server could not handle the request. */
+        public final String error;
+
+        /** REST http code. */
+        public final int code;
+
+        /** The field contains result of command. */
+        public final String data;
+
+        /**
+         * @param error The field contains description of error if server could not handle the request.
+         * @param resCode REST http code.
+         * @param res The field contains result of command.
+         */
+        private RestResult(String error, int resCode, String res) {
+            this.error = error;
+            this.code = resCode;
+            this.data = res;
+        }
+
+        /**
+         * @param error The field contains description of error if server could not handle the request.
+         * @param restCode REST http code.
+         * @return Request result.
+         */
+        public static RestResult fail(String error, int restCode) {
+            return new RestResult(error, restCode, null);
+        }
+
+        /**
+         * @param code REST http code.
+         * @param data The field contains result of command.
+         * @return Request result.
+         */
+        public static RestResult success(int code, String data) {
+            return new RestResult(null, code, data);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/Remote.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/Remote.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/Remote.java
deleted file mode 100644
index 71b2bc0..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/Remote.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent.remote;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Use this annotation to associate methods with remote NodeJS server commands.
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Remote {
-    /**
-     * Whether or not method should be executed synchronously.
-     *
-     * @return {@code true} if method will be executed in separated thread otherwise if method will be executed in handler thread.
-     */
-    boolean async() default true;
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
deleted file mode 100644
index 11e35e3..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/RemoteHandler.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent.remote;
-
-import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonNull;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Type;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionException;
-
-import org.apache.http.auth.AuthenticationException;
-import org.apache.log4j.Logger;
-
-/**
- * Allow to execute methods remotely from NodeJS server by web-socket command.
- */
-public class RemoteHandler implements AutoCloseable {
-    /** */
-    public static final Gson GSON = new Gson();
-
-    /** */
-    public static final Object[] EMPTY_OBJECTS = new Object[0];
-
-    /** */
-    private static final Logger log = Logger.getLogger(RemoteHandler.class.getName());
-
-    /** */
-    private static final String INTERNAL_EXCEPTION_TYPE = "org.apache.ignite.agent.AgentException";
-
-    /** */
-    private final WebSocketSender snd;
-
-    /** */
-    private final Map<String, MethodDescriptor> mtds = new HashMap<>();
-
-    /** */
-    private final ExecutorService executorSrvc = Executors.newFixedThreadPool(Runtime.getRuntime()
-        .availableProcessors());
-
-    /**
-     * @param snd Session.
-     * @param hnds Handlers.
-     */
-    private RemoteHandler(WebSocketSender snd, Object ... hnds) {
-        this.snd = snd;
-
-        for (Object hnd : hnds) {
-            for (Method method : hnd.getClass().getMethods()) {
-                Remote ann = method.getAnnotation(Remote.class);
-
-                if (ann != null) {
-                    MethodDescriptor old = mtds.put(method.getName(), new MethodDescriptor(method, hnd, ann.async()));
-
-                    if (old != null)
-                        throw new IllegalArgumentException("Duplicated method: " + method.getName());
-                }
-            }
-        }
-    }
-
-    /**
-     * @param hnds Handler.
-     * @param snd Sender.
-     */
-    public static RemoteHandler wrap(WebSocketSender snd, Object ... hnds) {
-        return new RemoteHandler(snd, hnds);
-    }
-
-    /**
-     * @param req Request.
-     */
-    public void onMessage(JsonObject req) {
-        JsonPrimitive reqIdJson = req.getAsJsonPrimitive("reqId");
-
-        final Long reqId = reqIdJson == null ? null : reqIdJson.getAsLong();
-
-        String method = req.getAsJsonPrimitive("method").getAsString();
-
-        final MethodDescriptor desc = mtds.get(method);
-
-        if (desc == null) {
-            sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Unknown method: " + method);
-
-            return;
-        }
-
-        Type[] paramTypes = desc.mtd.getGenericParameterTypes();
-
-        JsonArray argsJson = req.getAsJsonArray("args");
-
-        final Object[] args;
-
-        if (paramTypes.length > 0) {
-            args = new Object[paramTypes.length];
-
-            if (argsJson == null || argsJson.size() != paramTypes.length) {
-                sendException(reqId, INTERNAL_EXCEPTION_TYPE, "Inconsistent parameters count");
-
-                return;
-            }
-
-            for (int i = 0; i < paramTypes.length; i++)
-                args[i] = GSON.fromJson(argsJson.get(i), paramTypes[i]);
-        }
-        else
-            args = EMPTY_OBJECTS;
-
-        Runnable run = new Runnable() {
-            @Override public void run() {
-                final Object res;
-
-                try {
-                    res = desc.mtd.invoke(desc.hnd, args);
-                }
-                catch (Throwable e) {
-                    if (e instanceof AuthenticationException) {
-                        close();
-
-                        return;
-                    }
-
-                    if (e instanceof InvocationTargetException)
-                        e = ((InvocationTargetException)e).getTargetException();
-
-                    if (reqId != null)
-                        sendException(reqId, e.getClass().getName(), e.getMessage());
-                    else
-                        log.error("Exception on execute remote method.", e);
-
-                    return;
-                }
-
-                sendResponse(reqId, res, desc.returnType);
-            }
-        };
-
-        if (desc.async) {
-            try {
-                executorSrvc.submit(run);
-            }
-            catch (RejectedExecutionException ignore) {
-                // No-op.
-            }
-        }
-        else
-            run.run();
-    }
-
-    /**
-     * @param reqId Request id.
-     * @param exType Exception class name.
-     * @param exMsg Exception message.
-     */
-    protected void sendException(Long reqId, String exType, String exMsg) {
-        if (reqId == null)
-            return;
-
-        JsonObject res = new JsonObject();
-
-        res.addProperty("type", "CallRes");
-        res.addProperty("reqId", reqId);
-        res.addProperty("error", exType + ": " + exMsg);
-
-        snd.send(res);
-    }
-
-    /**
-     * @param reqId Request id.
-     * @param res Result.
-     * @param type Type.
-     */
-    private void sendResponse(Long reqId, Object res, Type type) {
-        if (reqId == null)
-            return;
-
-        JsonObject resp = new JsonObject();
-
-        resp.addProperty("type", "CallRes");
-        resp.addProperty("reqId", reqId);
-
-        JsonElement resJson = type == void.class ? JsonNull.INSTANCE : GSON.toJsonTree(res, type);
-
-        resp.add("response", resJson);
-
-        snd.send(resp);
-    }
-
-    /** {@inheritDoc} */
-    @Override public void close() {
-        executorSrvc.shutdown();
-    }
-
-    /**
-     *
-     */
-    private static class MethodDescriptor {
-        /** */
-        private final Method mtd;
-
-        /** */
-        private final Object hnd;
-
-        /** */
-        private final Type returnType;
-
-        /** */
-        private final boolean async;
-
-        /**
-         * @param mtd Method.
-         * @param hnd Handler.
-         * @param async Async.
-         */
-        MethodDescriptor(Method mtd, Object hnd, boolean async) {
-            this.mtd = mtd;
-            this.hnd = hnd;
-            this.async = async;
-
-            returnType = mtd.getGenericReturnType();
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java
deleted file mode 100644
index cceb86b..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/agent/remote/WebSocketSender.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.console.agent.remote;
-
-import com.google.gson.JsonObject;
-
-/**
- * Sender for messages to web-socket.
- */
-public interface WebSocketSender {
-    /**
-     * Send message.
-     * @param msg Message.
-     * @return {@code true} if message sent successfully.
-     */
-    public boolean send(String msg);
-
-    /**
-     * Send message.
-     * @param msg Message.
-     * @return {@code true} if message sent successfully.
-     */
-    public boolean send(JsonObject msg);
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/706317f3/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
index 5d33f29..4683dd8 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/console/demo/AgentMetadataDemo.java
@@ -18,11 +18,12 @@
 package org.apache.ignite.console.demo;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.sql.Connection;
 import java.sql.DriverManager;
+import java.sql.SQLException;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import org.apache.log4j.Logger;
 import org.h2.tools.RunScript;
 import org.h2.tools.Server;
@@ -52,7 +53,7 @@ public class AgentMetadataDemo {
     /**
      * Start H2 database and populate it with several tables.
      */
-    public static void testDrive() {
+    public static Connection testDrive() throws SQLException {
         if (initLatch.compareAndSet(false, true)) {
             log.info("DEMO: Prepare in-memory H2 database...");
 
@@ -61,13 +62,7 @@ public class AgentMetadataDemo {
 
                 File sqlScript = resolvePath("demo/db-init.sql");
 
-                if (sqlScript == null) {
-                    log.error("DEMO: Failed to find demo database init script file: demo/db-init.sql");
-                    log.error("DEMO: Failed to start demo for metadata");
-
-                    return;
-                }
-
+                //noinspection ConstantConditions
                 RunScript.execute(conn, new FileReader(sqlScript));
 
                 log.info("DEMO: Sample tables created.");
@@ -80,9 +75,18 @@ public class AgentMetadataDemo {
 
                 log.info("DEMO: JDBC URL for test drive metadata load: jdbc:h2:mem:demo-db");
             }
-            catch (Exception e) {
+            catch (SQLException e) {
                 log.error("DEMO: Failed to start test drive for metadata!", e);
+
+                throw e;
+            }
+            catch (FileNotFoundException | NullPointerException e) {
+                log.error("DEMO: Failed to find demo database init script file: demo/db-init.sql");
+
+                throw new SQLException("Failed to start demo for metadata", e);
             }
         }
+
+        return DriverManager.getConnection("jdbc:h2:mem:demo-db;DB_CLOSE_DELAY=-1", "sa", "");
     }
 }