You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by se...@apache.org on 2015/07/29 20:05:31 UTC

incubator-ignite git commit: IGNITE-1155 Added API to extract DB meta-data , deep refactoring of agent.

Repository: incubator-ignite
Updated Branches:
  refs/heads/ignite-1155_1 a21d96ea5 -> 8a6b5dddf


IGNITE-1155 Added API to extract DB meta-data , deep refactoring of agent.


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

Branch: refs/heads/ignite-1155_1
Commit: 8a6b5dddfca787ebcb9a6c59c881a2e685be4130
Parents: a21d96e
Author: sevdokimov <se...@gridgain.com>
Authored: Wed Jul 29 21:04:44 2015 +0300
Committer: sevdokimov <se...@gridgain.com>
Committed: Wed Jul 29 21:04:44 2015 +0300

----------------------------------------------------------------------
 .../java/org/apache/ignite/agent/Agent.java     | 164 ------------
 .../org/apache/ignite/agent/AgentLauncher.java  |   8 +-
 .../org/apache/ignite/agent/AgentSocket.java    | 103 ++++----
 .../org/apache/ignite/agent/RestExecutor.java   | 165 ++++++++++++
 .../java/org/apache/ignite/agent/Utils.java     |  41 +++
 .../apache/ignite/agent/WebSocketSender.java    |  39 +++
 .../ignite/agent/messages/AbstractMessage.java  |  25 --
 .../ignite/agent/messages/AuthMessage.java      |  73 ------
 .../ignite/agent/messages/AuthResult.java       |  57 -----
 .../agent/messages/DbMetadataRequest.java       | 110 --------
 .../agent/messages/DbMetadataResponse.java      |  61 -----
 .../ignite/agent/messages/MessageFactory.java   |  76 ------
 .../ignite/agent/messages/RestRequest.java      | 127 ----------
 .../ignite/agent/messages/RestResult.java       |  91 -------
 .../org/apache/ignite/agent/remote/Remote.java  |  34 +++
 .../ignite/agent/remote/RemoteCallable.java     | 248 +++++++++++++++++++
 .../src/main/js/agents/agent-manager.js         | 172 +++++++------
 .../src/main/js/agents/agent-server.js          |   8 +-
 18 files changed, 693 insertions(+), 909 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Agent.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Agent.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Agent.java
deleted file mode 100644
index 306e21a..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Agent.java
+++ /dev/null
@@ -1,164 +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.agent;
-
-import org.apache.commons.codec.*;
-import org.apache.http.*;
-import org.apache.http.client.entity.*;
-import org.apache.http.client.methods.*;
-import org.apache.http.client.utils.*;
-import org.apache.http.entity.*;
-import org.apache.http.impl.client.*;
-import org.apache.ignite.agent.messages.*;
-import org.apache.ignite.schema.parser.*;
-
-import java.io.*;
-import java.net.*;
-import java.nio.charset.*;
-import java.sql.*;
-import java.util.*;
-
-/**
- *
- */
-public class Agent {
-    /** */
-    private final AgentConfiguration cfg;
-
-    /** */
-    private CloseableHttpClient httpClient;
-
-    /**
-     * @param cfg Config.
-     */
-    public Agent(AgentConfiguration cfg) {
-        this.cfg = cfg;
-    }
-
-    /**
-     *
-     */
-    public void start() {
-        httpClient = HttpClientBuilder.create().build();
-    }
-
-    /**
-     *
-     */
-    public void stop() throws IOException {
-        if (httpClient != null)
-            httpClient.close();
-    }
-
-    /**
-     * @param restReq Request.
-     */
-    public RestResult executeRest(RestRequest restReq) throws IOException, URISyntaxException {
-        URIBuilder builder = new URIBuilder(cfg.getNodeUri());
-
-        String path = restReq.getPath();
-
-        if (path != null) {
-            if (!path.startsWith("/") && !cfg.getNodeUri().toString().endsWith("/"))
-                path = '/' +  path;
-
-            builder.setPath(path);
-        }
-
-        if (restReq.getParams() != null) {
-            for (Map.Entry<String, String> entry : restReq.getParams().entrySet())
-                builder.addParameter(entry.getKey(), entry.getValue());
-        }
-
-        if (restReq.getHeaders() != null)
-            restReq.setHeaders(restReq.getHeaders());
-
-        HttpRequestBase httpReq;
-
-        if ("GET".equalsIgnoreCase(restReq.getMethod()))
-            httpReq = new HttpGet(builder.build());
-        else if ("POST".equalsIgnoreCase(restReq.getMethod())) {
-            HttpPost post;
-
-            if (restReq.getBody() == 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(restReq.getBody()));
-            }
-
-            httpReq = post;
-        }
-        else
-            throw new IOException("Unknown HTTP-method: " + restReq.getMethod());
-
-        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);
-            }
-
-            RestResult res = new RestResult();
-
-            res.setCode(resp.getStatusLine().getStatusCode());
-            res.setExecuted(true);
-            res.setMessage(new String(out.toByteArray(), charset));
-
-            return res;
-        }
-    }
-
-    /**
-     * @param req Request.
-     */
-    public DbMetadataResponse dbMetadataRequest(DbMetadataRequest req) {
-        DbMetadataResponse res = new DbMetadataResponse();
-
-        try {
-            Connection conn = DBReader.getInstance().connect(req.getJdbcDriverJarPath(), req.getJdbcDriverClass(),
-                req.getJdbcUrl(), req.getJdbcInfo());
-
-            Collection<DbTable> tbls = DBReader.getInstance().extractMetadata(conn, req.isTablesOnly());
-
-            res.setTables(tbls);
-        }
-        catch (SQLException e) {
-            res.setError(e.getMessage());
-        }
-
-        return res;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
index c1df0fe..f1391b1 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentLauncher.java
@@ -85,9 +85,9 @@ public class AgentLauncher {
     public static void main(String[] args) throws Exception {
         AgentConfiguration cfg = getConfiguration(args);
 
-        Agent agent = new Agent(cfg);
+        RestExecutor restExecutor = new RestExecutor(cfg);
 
-        agent.start();
+        restExecutor.start();
 
         try {
             SslContextFactory sslCtxFactory = new SslContextFactory();
@@ -103,7 +103,7 @@ public class AgentLauncher {
 
             try {
                 while (true) {
-                    AgentSocket agentSock = new AgentSocket(cfg, agent);
+                    AgentSocket agentSock = new AgentSocket(cfg, restExecutor);
 
                     log.log(Level.INFO, "Connecting to: " + cfg.getServerUri());
 
@@ -119,7 +119,7 @@ public class AgentLauncher {
             }
         }
         finally {
-            agent.stop();
+            restExecutor.stop();
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
index 3c1c59a..1581ee6 100644
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/AgentSocket.java
@@ -17,13 +17,16 @@
 
 package org.apache.ignite.agent;
 
-import org.apache.http.*;
-import org.apache.ignite.agent.messages.*;
+import com.google.gson.*;
+import org.apache.ignite.agent.remote.*;
+import org.apache.ignite.schema.parser.*;
 import org.eclipse.jetty.websocket.api.*;
 import org.eclipse.jetty.websocket.api.annotations.*;
 
 import java.io.*;
 import java.net.*;
+import java.sql.*;
+import java.util.*;
 import java.util.concurrent.*;
 import java.util.logging.*;
 
@@ -31,7 +34,7 @@ import java.util.logging.*;
  *
  */
 @WebSocket
-public class AgentSocket {
+public class AgentSocket implements WebSocketSender {
     /** */
     private static final Logger log = Logger.getLogger(AgentSocket.class.getName());
 
@@ -42,7 +45,10 @@ public class AgentSocket {
     private final AgentConfiguration cfg;
 
     /** */
-    private final Agent agent;
+    private final RestExecutor restExecutor;
+
+    /** */
+    private RemoteCallable remote;
 
     /** */
     private Session ses;
@@ -50,9 +56,9 @@ public class AgentSocket {
     /**
      * @param cfg Config.
      */
-    public AgentSocket(AgentConfiguration cfg, Agent agent) {
+    public AgentSocket(AgentConfiguration cfg, RestExecutor restExecutor) {
         this.cfg = cfg;
-        this.agent = agent;
+        this.restExecutor = restExecutor;
     }
 
     /**
@@ -63,6 +69,9 @@ public class AgentSocket {
     public void onClose(int statusCode, String reason) {
         log.log(Level.INFO, String.format("Connection closed: %d - %s", statusCode, reason));
 
+        if (remote != null)
+            remote.close();
+
         closeLatch.countDown();
     }
 
@@ -75,20 +84,22 @@ public class AgentSocket {
 
         this.ses = ses;
 
-        AuthMessage authMsg = new AuthMessage(cfg.getLogin(), cfg.getPassword());
+        remote = RemoteCallable.wrap(this, this, restExecutor);
 
-        try {
-            ses.getRemote().sendString(MessageFactory.toString(authMsg));
-        } catch (IOException t) {
-            t.printStackTrace();
-        }
+        JsonObject authMsg = new JsonObject();
+
+        authMsg.addProperty("type", "AuthMessage");
+        authMsg.addProperty("login", cfg.getLogin());
+        authMsg.addProperty("password", cfg.getPassword());
+
+        send(authMsg);
     }
 
     /**
      * @param msg Message.
      */
-    public boolean send(AbstractMessage msg) {
-        return send(MessageFactory.toString(msg));
+    public boolean send(JsonObject msg) {
+        return send(Utils.GSON.toJson(msg));
     }
 
     /**
@@ -118,6 +129,9 @@ public class AgentSocket {
         else
             log.log(Level.SEVERE, "Connection error", error);
 
+        if (remote != null)
+            remote.close();
+
         closeLatch.countDown();
     }
 
@@ -126,43 +140,40 @@ public class AgentSocket {
      */
     @OnWebSocketMessage
     public void onMessage(Session ses, String msg) {
-        AbstractMessage m = MessageFactory.fromString(msg);
+        JsonElement jsonElement = Utils.PARSER.parse(msg);
 
-        if (m instanceof AuthResult) {
-            if (((AuthResult)m).isSuccess())
-                log.info("Authentication success");
-            else {
-                log.info("Authentication failed: " + ((AuthResult)m).getMessage());
+        remote.onMessage((JsonObject)jsonElement);
+    }
 
-                ses.close();
-            }
+    /**
+     * @param errorMsg Authentication failed message or {@code null} if authentication succes.
+     */
+    @Remote
+    public void authResult(String errorMsg) {
+        if (errorMsg == null)
+            log.info("Authentication success");
+        else {
+            log.info("Authentication failed: " + errorMsg);
+
+            ses.close();
         }
-        else if (m instanceof RestRequest) {
-            RestRequest restReq = (RestRequest)m;
-
-            RestResult restRes;
-
-            try {
-                restRes = agent.executeRest(restReq);
-            }
-            catch (Exception e) {
-                restRes = new RestResult();
-
-                restRes.setCode(HttpStatus.SC_INTERNAL_SERVER_ERROR);
-                restRes.setMessage(e.getMessage());
-            }
-
-            restRes.setRequestId(((RestRequest)m).getId());
+    }
 
-            send(MessageFactory.toString(restRes));
-        }
-        else if (m instanceof DbMetadataRequest) {
-            DbMetadataResponse resp = agent.dbMetadataRequest((DbMetadataRequest)m);
+    /**
+     *
+     * @param jdbcDriverJarPath JDBC driver JAR path.
+     * @param jdbcDriverClass JDBC driver class.
+     * @param jdbcUrl JDBC URL.
+     * @param jdbcInfo Properties to connect to database.
+     *
+     * @return Collection of tables.
+     */
+    @Remote
+    public Collection<DbTable> extractMetadata(String jdbcDriverJarPath, String jdbcDriverClass, String jdbcUrl,
+        Properties jdbcInfo, boolean tablesOnly) throws SQLException {
+        Connection conn = DBReader.getInstance().connect(jdbcDriverJarPath, jdbcDriverClass, jdbcUrl, jdbcInfo);
 
-            send(resp);
-        }
-        else
-            log.log(Level.SEVERE, "Unknown message: " + msg);
+        return DBReader.getInstance().extractMetadata(conn, tablesOnly);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/RestExecutor.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/RestExecutor.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/RestExecutor.java
new file mode 100644
index 0000000..b264adb
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/RestExecutor.java
@@ -0,0 +1,165 @@
+/*
+ * 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.agent;
+
+import org.apache.commons.codec.*;
+import org.apache.http.*;
+import org.apache.http.client.entity.*;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.utils.*;
+import org.apache.http.entity.*;
+import org.apache.http.impl.client.*;
+import org.apache.ignite.agent.remote.*;
+
+import java.io.*;
+import java.net.*;
+import java.nio.charset.*;
+import java.util.*;
+import java.util.logging.*;
+
+/**
+ *
+ */
+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 path Path.
+     * @param method Method.
+     * @param params Params.
+     * @param headers Headers.
+     * @param body Body.
+     */
+    @Remote
+    public RestResult executeRest(String path, Map<String, String> params, String method, Map<String, String> headers,
+        String body) throws IOException, URISyntaxException {
+        URIBuilder builder = new URIBuilder(cfg.getNodeUri());
+
+        if (path != null) {
+            if (!path.startsWith("/") && !cfg.getNodeUri().toString().endsWith("/"))
+                path = '/' +  path;
+
+            builder.setPath(path);
+        }
+
+        if (params != null) {
+            for (Map.Entry<String, String> entry : params.entrySet())
+                builder.addParameter(entry.getKey(), entry.getValue());
+        }
+
+        HttpRequestBase httpReq;
+
+        if ("GET".equalsIgnoreCase(method))
+            httpReq = new HttpGet(builder.build());
+        else if ("POST".equalsIgnoreCase(method)) {
+            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: " + method);
+
+        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 new RestResult(resp.getStatusLine().getStatusCode(), new String(out.toByteArray(), charset));
+        }
+    }
+
+    /**
+     *
+     */
+    public static class RestResult {
+        /** */
+        private int code;
+
+        /** */
+        private String message;
+
+        /**
+         * @param code Code.
+         * @param msg Message.
+         */
+        public RestResult(int code, String msg) {
+            this.code = code;
+            message = msg;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Utils.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Utils.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Utils.java
new file mode 100644
index 0000000..b86e1df
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/Utils.java
@@ -0,0 +1,41 @@
+/*
+ * 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.agent;
+
+import com.google.gson.*;
+
+/**
+ *
+ */
+public class Utils {
+    /** */
+    public static final Gson GSON = new Gson();
+
+    /** */
+    public static final JsonParser PARSER = new JsonParser();
+
+    /** */
+    public static final Object[] EMPTY_OBJECTS = new Object[0];
+
+    /**
+     * Default constructor.
+     */
+    private Utils() {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/WebSocketSender.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/WebSocketSender.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/WebSocketSender.java
new file mode 100644
index 0000000..6d16c14
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/WebSocketSender.java
@@ -0,0 +1,39 @@
+/*
+ * 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.agent;
+
+import com.google.gson.*;
+
+/**
+ *
+ */
+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/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AbstractMessage.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AbstractMessage.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AbstractMessage.java
deleted file mode 100644
index e289272..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AbstractMessage.java
+++ /dev/null
@@ -1,25 +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.agent.messages;
-
-/**
- *
- */
-public abstract class AbstractMessage {
-    // No-op.
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthMessage.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthMessage.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthMessage.java
deleted file mode 100644
index 9a32a99..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthMessage.java
+++ /dev/null
@@ -1,73 +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.agent.messages;
-
-/**
- *
- */
-public class AuthMessage extends AbstractMessage {
-    /** */
-    private String login;
-
-    /** */
-    private String password;
-
-    /**
-     * Default constructor.
-     */
-    public AuthMessage() {
-        // No-op.
-    }
-
-    /**
-     * @param login Login.
-     * @param pwd Password.
-     */
-    public AuthMessage(String login, String pwd) {
-        this.login = login;
-        password = pwd;
-    }
-
-    /**
-     *
-     */
-    public String getLogin() {
-        return login;
-    }
-
-    /**
-     * @param login Login.
-     */
-    public void setLogin(String login) {
-        this.login = login;
-    }
-
-    /**
-     *
-     */
-    public String getPassword() {
-        return password;
-    }
-
-    /**
-     * @param pwd Password.
-     */
-    public void setPassword(String pwd) {
-        password = pwd;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthResult.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthResult.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthResult.java
deleted file mode 100644
index 5513193..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/AuthResult.java
+++ /dev/null
@@ -1,57 +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.agent.messages;
-
-/**
- *
- */
-public class AuthResult extends AbstractMessage {
-    /** */
-    private boolean success;
-
-    /** */
-    private String message;
-
-    /**
-     *
-     */
-    public boolean isSuccess() {
-        return success;
-    }
-
-    /**
-     * @param success Success.
-     */
-    public void setSuccess(boolean success) {
-        this.success = success;
-    }
-
-    /**
-     *
-     */
-    public String getMessage() {
-        return message;
-    }
-
-    /**
-     * @param msg Message.
-     */
-    public void setMessage(String msg) {
-        message = msg;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataRequest.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataRequest.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataRequest.java
deleted file mode 100644
index 82e83e4..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataRequest.java
+++ /dev/null
@@ -1,110 +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.agent.messages;
-
-import java.util.*;
-
-/**
- *
- */
-public class DbMetadataRequest extends AbstractMessage {
-    /** */
-    private String jdbcDriverJarPath;
-
-    /** */
-    private String jdbcDriverClass;
-
-    /** */
-    private String jdbcUrl;
-
-    /** */
-    private Properties jdbcInfo;
-
-    /** */
-    private boolean tablesOnly;
-
-    /**
-     * @return JDBC driver class name.
-     */
-    public String getJdbcDriverClass() {
-        return jdbcDriverClass;
-    }
-
-    /**
-     * @param jdbcDriverCls Jdbc driver class.
-     */
-    public void setJdbcDriverClass(String jdbcDriverCls) {
-        this.jdbcDriverClass = jdbcDriverCls;
-    }
-
-    /**
-     *
-     */
-    public String getJdbcUrl() {
-        return jdbcUrl;
-    }
-
-    /**
-     * @param jdbcUrl Jdbc url.
-     */
-    public void setJdbcUrl(String jdbcUrl) {
-        this.jdbcUrl = jdbcUrl;
-    }
-
-    /**
-     *
-     */
-    public Properties getJdbcInfo() {
-        return jdbcInfo;
-    }
-
-    /**
-     * @param jdbcInfo Jdbc info.
-     */
-    public void setJdbcInfo(Properties jdbcInfo) {
-        this.jdbcInfo = jdbcInfo;
-    }
-
-    /**
-     *
-     */
-    public String getJdbcDriverJarPath() {
-        return jdbcDriverJarPath;
-    }
-
-    /**
-     * @param jdbcDriverJarPath Jdbc drv jar path.
-     */
-    public void setJdbcDriverJarPath(String jdbcDriverJarPath) {
-        this.jdbcDriverJarPath = jdbcDriverJarPath;
-    }
-
-    /**
-     * @return Tables only flag.
-     */
-    public boolean isTablesOnly() {
-        return tablesOnly;
-    }
-
-    /**
-     * @param tblsOnly Tables only.
-     */
-    public void setTablesOnly(boolean tblsOnly) {
-        this.tablesOnly = tblsOnly;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataResponse.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataResponse.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataResponse.java
deleted file mode 100644
index d6eec9d..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/DbMetadataResponse.java
+++ /dev/null
@@ -1,61 +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.agent.messages;
-
-import org.apache.ignite.schema.parser.*;
-
-import java.util.*;
-
-/**
- *
- */
-public class DbMetadataResponse extends AbstractMessage {
-    /** */
-    private String error;
-
-    /** */
-    private Collection<DbTable> tables;
-
-    /**
-     *
-     */
-    public String getError() {
-        return error;
-    }
-
-    /**
-     * @param error Error.
-     */
-    public void setError(String error) {
-        this.error = error;
-    }
-
-    /**
-     *
-     */
-    public Collection<DbTable> getTables() {
-        return tables;
-    }
-
-    /**
-     * @param tbls Tables.
-     */
-    public void setTables(Collection<DbTable> tbls) {
-        this.tables = tbls;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/MessageFactory.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/MessageFactory.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/MessageFactory.java
deleted file mode 100644
index d6cc0b9..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/MessageFactory.java
+++ /dev/null
@@ -1,76 +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.agent.messages;
-
-import com.google.gson.*;
-
-/**
- *
- */
-public class MessageFactory {
-    /** */
-    private static final Gson gson = new Gson();
-
-    /** */
-    private static final JsonParser parser = new JsonParser();
-
-    /**
-     * Default constructor.
-     */
-    private MessageFactory() {
-        // No-op.
-    }
-
-    /**
-     * @param msg Message.
-     */
-    public static String toString(AbstractMessage msg) {
-        assert msg.getClass().getPackage().equals(MessageFactory.class.getPackage());
-
-        JsonObject json = (JsonObject)gson.toJsonTree(msg);
-
-        json.addProperty("type", msg.getClass().getSimpleName());
-
-        return gson.toJson(json);
-    }
-
-    /**
-     * @param jsonStr Json string.
-     */
-    public static AbstractMessage fromString(String jsonStr) {
-        JsonElement jsonElement = parser.parse(jsonStr);
-
-        if (!(jsonElement instanceof JsonObject))
-            throw new IllegalArgumentException();
-
-        JsonObject json = (JsonObject)jsonElement;
-
-        String type = json.getAsJsonPrimitive("type").getAsString();
-
-        Class<? extends AbstractMessage> cls;
-
-        try {
-            cls = (Class<? extends AbstractMessage>)Class.forName(MessageFactory.class.getPackage().getName() + '.' + type);
-        }
-        catch (ClassNotFoundException e) {
-            throw new IllegalArgumentException(e);
-        }
-
-        return gson.fromJson(json, cls);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestRequest.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestRequest.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestRequest.java
deleted file mode 100644
index 9c06332..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestRequest.java
+++ /dev/null
@@ -1,127 +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.agent.messages;
-
-import java.util.*;
-
-/**
- *
- */
-public class RestRequest extends AbstractMessage {
-    /** */
-    private int id;
-
-    /** */
-    private String path;
-
-    /** */
-    private Map<String, String> params;
-
-    /** */
-    private String method;
-
-    /** */
-    private Map<String, String> headers;
-
-    /** */
-    private String body;
-
-    /**
-     *
-     */
-    public int getId() {
-        return id;
-    }
-
-    /**
-     * @param id Id.
-     */
-    public void setId(int id) {
-        this.id = id;
-    }
-
-    /**
-     *
-     */
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * @param path Url.
-     */
-    public void setPath(String path) {
-        this.path = path;
-    }
-
-    /**
-     *
-     */
-    public Map<String, String> getParams() {
-        return params;
-    }
-
-    /**
-     * @param params Params.
-     */
-    public void setParams(Map<String, String> params) {
-        this.params = params;
-    }
-
-    /**
-     *
-     */
-    public String getMethod() {
-        return method;
-    }
-
-    /**
-     * @param mtd Method.
-     */
-    public void setMethod(String mtd) {
-        method = mtd;
-    }
-
-    /**
-     *
-     */
-    public Map<String, String> getHeaders() {
-        return headers;
-    }
-
-    /**
-     * @param headers Headers.
-     */
-    public void setHeaders(Map<String, String> headers) {
-        this.headers = headers;
-    }
-
-    /**
-     *
-     */
-    public String getBody() {
-        return body;
-    }
-
-    /**
-     * @param body Body.
-     */
-    public void setBody(String body) {
-        this.body = body;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestResult.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestResult.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestResult.java
deleted file mode 100644
index 16ffa1b..0000000
--- a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/messages/RestResult.java
+++ /dev/null
@@ -1,91 +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.agent.messages;
-
-/**
- *
- */
-public class RestResult extends AbstractMessage {
-    /** */
-    private int requestId;
-
-    /** */
-    private boolean executed;
-
-    /** */
-    private int code;
-
-    /** */
-    private String message;
-
-    /**
-     *
-     */
-    public int getRequestId() {
-        return requestId;
-    }
-
-    /**
-     * @param reqId Request id.
-     */
-    public void setRequestId(int reqId) {
-        requestId = reqId;
-    }
-
-    /**
-     *
-     */
-    public int getCode() {
-        return code;
-    }
-
-    /**
-     * @param code Code.
-     */
-    public void setCode(int code) {
-        this.code = code;
-    }
-
-    /**
-     *
-     */
-    public String getMessage() {
-        return message;
-    }
-
-    /**
-     * @param msg Message.
-     */
-    public void setMessage(String msg) {
-        message = msg;
-    }
-
-    /**
-     *
-     */
-    public boolean isExecuted() {
-        return executed;
-    }
-
-    /**
-     * @param executed Executed.
-     */
-    public void setExecuted(boolean executed) {
-        this.executed = executed;
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
new file mode 100644
index 0000000..d7950bf
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/Remote.java
@@ -0,0 +1,34 @@
+/*
+ * 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.agent.remote;
+
+import java.lang.annotation.*;
+
+/**
+ * Method annotated by this annotation can be executed remotely from NodeJS server by web-socket command.
+ */
+@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,
+     * {@code false} if method executed in web-socket thread.
+     */
+    boolean async() default true;
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteCallable.java
----------------------------------------------------------------------
diff --git a/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteCallable.java b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteCallable.java
new file mode 100644
index 0000000..84dbc79
--- /dev/null
+++ b/modules/control-center-agent/src/main/java/org/apache/ignite/agent/remote/RemoteCallable.java
@@ -0,0 +1,248 @@
+/*
+ * 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.agent.remote;
+
+import com.google.gson.*;
+import org.apache.ignite.agent.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.logging.*;
+
+/**
+ * Allow to execute methods remotely from NodeJS server by web-socket command.
+ */
+public class RemoteCallable implements AutoCloseable {
+    /** */
+    private static final Logger log = Logger.getLogger(RemoteCallable.class.getName());
+
+    /** */
+    private final WebSocketSender snd;
+
+    /** */
+    private final Map<String, MethodDescriptor> methods = new HashMap<>();
+
+    /** */
+    private final ExecutorService executorSrvc = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+
+    /**
+     * @param snd Session.
+     * @param hnds Handlers.
+     */
+    private RemoteCallable(WebSocketSender snd, Object ... hnds) {
+        this.snd = snd;
+
+        for (Object hnd : hnds) {
+            for (Method method : hnd.getClass().getMethods()) {
+                Remote remoteAnn = method.getAnnotation(Remote.class);
+
+                if (remoteAnn != null) {
+                    MethodDescriptor old = methods.put(method.getName(), new MethodDescriptor(method, hnd, remoteAnn.async()));
+
+                    if (old != null)
+                        throw new IllegalArgumentException("Duplicated method: " + method.getName());
+                }
+            }
+        }
+
+    }
+
+    /**
+     * @param req Request.
+     */
+    public void onMessage(JsonObject req) {
+        if (log.isLoggable(Level.FINE))
+            log.fine("Message: " + req);
+
+        JsonPrimitive reqIdJson = req.getAsJsonPrimitive("reqId");
+
+        final Long reqId = reqIdJson == null ? null : reqIdJson.getAsLong();
+
+        String mtdName = req.getAsJsonPrimitive("mtdName").getAsString();
+
+        final MethodDescriptor desc = methods.get(mtdName);
+
+        if (desc == null) {
+            sendError(reqId, "Unknown method: " + mtdName);
+
+            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) {
+                sendError(reqId, "Inconsistent parameters");
+
+                return;
+            }
+
+            for (int i = 0; i < paramTypes.length; i++)
+                args[i] = Utils.GSON.fromJson(argsJson.get(i), paramTypes[i]);
+        }
+        else {
+            args = Utils.EMPTY_OBJECTS;
+
+            if (argsJson != null && argsJson.size() > 0) {
+                sendError(reqId, "Inconsistent parameters");
+
+                return;
+            }
+        }
+
+        Runnable run = new Runnable() {
+            @Override public void run() {
+                final Object res;
+
+                try {
+                    res = desc.mtd.invoke(desc.hnd, args);
+                } catch (Exception e) {
+                    if (reqId != null)
+                        sendException(reqId, e);
+                    else
+                        log.log(Level.SEVERE, "Exception on execute remote method", e);
+
+                    return;
+                }
+
+                sendResponse(reqId, res, desc.returnType);
+            }
+        };
+
+        if (desc.async)
+            executorSrvc.submit(run);
+        else
+            run.run();
+    }
+
+    /**
+     * @param reqId Request id.
+     * @param ex Exception.
+     */
+    protected void sendException(Long reqId, Exception ex) {
+        if (reqId == null)
+            return;
+
+        JsonObject res = new JsonObject();
+
+        res.addProperty("type", "CallRes");
+        res.addProperty("reqId", reqId);
+
+        JsonObject exJson = new JsonObject();
+        exJson.addProperty("type", ex.getClass().getName());
+        exJson.addProperty("message", ex.getMessage());
+
+        res.add("ex", exJson);
+
+        snd.send(res);
+    }
+
+    /**
+     * @param reqId Request id.
+     * @param err Exception.
+     */
+    protected void sendError(Long reqId, String err) {
+        if (reqId == null)
+            return;
+
+        JsonObject res = new JsonObject();
+
+        res.addProperty("type", "CallRes");
+        res.addProperty("reqId", reqId);
+        res.addProperty("error", err);
+
+        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;
+
+        if (type == void.class)
+            resJson = JsonNull.INSTANCE;
+        else
+            resJson = Utils.GSON.toJsonTree(res, type);
+
+        resp.add("res", resJson);
+
+        snd.send(resp);
+    }
+
+    /** {@inheritDoc} */
+    public void close() {
+        executorSrvc.shutdown();
+    }
+
+    /**
+     * @param hnds Handler.
+     * @param snd Sender.
+     */
+    public static RemoteCallable wrap(WebSocketSender snd, Object ... hnds) {
+        return new RemoteCallable(snd, hnds);
+    }
+
+    /**
+     *
+     */
+    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.
+         */
+        public 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/incubator-ignite/blob/8a6b5ddd/modules/web-control-center/src/main/js/agents/agent-manager.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/src/main/js/agents/agent-manager.js b/modules/web-control-center/src/main/js/agents/agent-manager.js
index 252b984..a35ca7a 100644
--- a/modules/web-control-center/src/main/js/agents/agent-manager.js
+++ b/modules/web-control-center/src/main/js/agents/agent-manager.js
@@ -119,36 +119,26 @@ function Client(ws, manager) {
         }
     });
 
-    ws.on('message', function (msg) {
-        self._handleMessage(JSON.parse(msg))
+    ws.on('message', function (msgStr) {
+        var msg = JSON.parse(msgStr);
+
+        self['_rmt' + msg.type](msg);
     });
 
-    this._restCounter = 0;
+    this._reqCounter = 0;
 
     this._cbMap = {};
 }
 
 /**
- * @param {String|Object} msg
- * @param {Function} cb
- */
-Client.prototype.sendMessage = function(msg, cb) {
-    if (typeof msg == 'object') {
-        msg = JSON.stringify(msg);
-    }
-
-    this._ws.send(msg, cb);
-};
-
-/**
  * @param {String} path
  * @param {Object} params
- * @param {Function} cb
- * @param {String} method
- * @param {String} body
- * @param {Object} headers
+ * @param {String} [method]
+ * @param {Object} [headers]
+ * @param {String} [body]
+ * @param {Function} [cb] Callback. Take 3 arguments: {String} error, {number} httpCode, {string} response.
  */
-Client.prototype.invokeRest = function(path, params, cb, method, body, headers) {
+Client.prototype.executeRest = function(path, params, method, headers, body, cb) {
     var self = this;
 
     if (typeof(params) != 'object')
@@ -171,79 +161,102 @@ Client.prototype.invokeRest = function(path, params, cb, method, body, headers)
     if (method != 'GET' && method != 'POST')
         throw "Unknown HTTP method: " + method;
 
-    var reqId = this._restCounter++;
+    var newArgs = argsToArray(arguments);
 
-    this._cbMap[reqId] = cb;
+    newArgs[5] = function(err, ex, res) {
+        if (err)
+            cb(err);
+        else if (ex)
+            cb(ex.message);
+        else
+            cb(null, res.code, res.message)
+    };
 
-    this.sendMessage({
-        id: reqId,
-        type: 'RestRequest',
-        method: method,
-        params: params,
-        path: path,
-        body: body,
-        headers: headers
-    }, function(err) {
-        if (err) {
-            delete self._cbMap[reqId];
+    this._invokeRmtMethod('executeRest', newArgs);
+};
 
-            cb(err)
-        }
-    })
+/**
+ * @param {string} error
+ */
+Client.prototype.authResult = function(error) {
+    this._invokeRmtMethod('authResult', arguments)
 };
 
 /**
- * @param {Object} msg
+ * @param {String} jdbcDriverJarPath
+ * @param {String} jdbcDriverClass
+ * @param {String} jdbcUrl
+ * @param {Object} jdbcInfo
+ * @param {Boolean} tablesOnly
+ * @param {Function} cb Callback. Take 3 arguments: {String} error, {Object} exception, {Object} result.
  */
-Client.prototype._handleMessage = function(msg) {
+Client.prototype.extractMetadata = function(jdbcDriverJarPath, jdbcDriverClass, jdbcUrl, jdbcInfo, tablesOnly, cb) {
+    this._invokeRmtMethod('extractMetadata', arguments)
+};
+
+Client.prototype._invokeRmtMethod = function(methodName, args) {
+    var cb = null;
+
+    var m = argsToArray(args);
+
+    if (m.length > 0 && typeof m[m.length - 1] == 'function')
+        cb = m.pop();
+
+    var msg = {
+        mtdName: methodName,
+        args: m
+    };
+
+    if (cb) {
+        var reqId = this._reqCounter++;
+
+        this._cbMap[reqId] = cb;
+
+        msg.reqId = reqId;
+    }
+
+    this._ws.send(JSON.stringify(msg))
+};
+
+Client.prototype._rmtAuthMessage = function(msg) {
     var self = this;
 
-    switch (msg.type) {
-        case 'AuthMessage':
-            var account = db.Account.findByUsername(msg.login, function(err, account) {
-                if (err) {
-                    ws.send("{type: 'AuthResult', success: false}");
+    var account = db.Account.findByUsername(msg.login, function(err, account) {
+        if (err) {
+            self.authResult("User not found");
+        }
+        else {
+            account.authenticate(msg.password, function(err, user, res) {
+                if (!user) {
+                    self.authResult(res.message);
                 }
                 else {
-                    account.authenticate(msg.password, function(err, user, res) {
-                        if (!user) {
-                            self._ws.send(JSON.stringify({type: 'AuthResult', success: false, message: res.message}));
-                        }
-                        else {
-                            self._ws.send("{type: 'AuthResult', success: true}");
+                    self.authResult(null);
 
-                            self._user = account;
+                    self._user = account;
 
-                            self._manager._addClient(account._id, self);
+                    self._manager._addClient(account._id, self);
 
-                            self._ignite = new ignite.Ignite(new AgentServer(self));
-                        }
-                    });
+                    self._ignite = new ignite.Ignite(new AgentServer(self));
                 }
             });
+        }
+    });
+};
 
-            break;
-
-        case 'RestResult':
-            var cb = this._cbMap[msg.requestId];
-
-            if (!cb)
-                break;
-
-            delete this._cbMap[msg.requestId];
+Client.prototype._rmtCallRes = function(msg) {
+    var cb = this._cbMap[msg.reqId];
 
-            if (!msg.executed) {
-                cb(msg.message)
-            }
-            else {
-                cb(null, msg.code, msg.message)
-            }
+    if (!cb) return;
 
-            break;
+    delete this._cbMap[msg.reqId];
 
-        default:
-            this._ws.close()
-    }
+    if (msg.error)
+        cb(msg.error);
+    else if (msg.ex)
+        cb(null, ex);
+    else
+        cb(null, null, msg.res);
 };
 
 /**
@@ -261,6 +274,19 @@ function removeFromArray(arr, val) {
     }
 }
 
+/**
+ * @param args
+ * @returns {Array}
+ */
+function argsToArray(args) {
+    var res = [];
+
+    for (var i = 0; i < args.length; i++)
+        res.push(args[i])
+
+    return res;
+}
+
 exports.AgentManager = AgentManager;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/8a6b5ddd/modules/web-control-center/src/main/js/agents/agent-server.js
----------------------------------------------------------------------
diff --git a/modules/web-control-center/src/main/js/agents/agent-server.js b/modules/web-control-center/src/main/js/agents/agent-server.js
index 31dee5a..842155f 100644
--- a/modules/web-control-center/src/main/js/agents/agent-server.js
+++ b/modules/web-control-center/src/main/js/agents/agent-server.js
@@ -44,13 +44,17 @@ AgentServer.prototype.runCommand = function(cmd, callback) {
 
     var headers = undefined;
 
+    var method = 'GET';
+
     if (cmd._isPost()) {
         body = cmd.postData();
 
+        method = 'POST';
+
         headers = {'Content-Length': body.length, 'JSONObject': 'application/json'};
     }
 
-    this._client.invokeRest("ignite", params, function(error, code, message) {
+    this._client.executeRest("ignite", params, method, headers, body, function(error, code, message) {
         if (error) {
             callback(error);
             return
@@ -84,7 +88,7 @@ AgentServer.prototype.runCommand = function(cmd, callback) {
         else {
             callback.call(null, null, igniteResponse.response);
         }
-    }, cmd._method(), body, headers);
+    });
 };
 
 exports.AgentServer = AgentServer;