IGNITE-4687 Added pool to process REST request in Web Agent.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/262a3410 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/262a3410 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/262a3410 Branch: refs/heads/ignite-3477-merge2.0 Commit: 262a3410802de790b75de9b94fea9599b30171bd Parents: 0ace63c Author: Andrey Novikov <anovi...@gridgain.com> Authored: Mon Feb 13 17:35:29 2017 +0700 Committer: Andrey Novikov <anovi...@gridgain.com> Committed: Mon Feb 13 17:35:29 2017 +0700 ---------------------------------------------------------------------- .../ignite/console/agent/AgentLauncher.java | 203 ++++++------ .../apache/ignite/console/agent/AgentUtils.java | 80 +++++ .../console/agent/handlers/AbstractHandler.java | 110 ------- .../agent/handlers/AbstractListener.java | 104 ++++++ .../console/agent/handlers/DatabaseHandler.java | 298 ----------------- .../agent/handlers/DatabaseListener.java | 316 +++++++++++++++++++ .../console/agent/handlers/RestHandler.java | 276 ---------------- .../console/agent/handlers/RestListener.java | 280 ++++++++++++++++ 8 files changed, 880 insertions(+), 787 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java index 049791f..a3d609f 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentLauncher.java @@ -41,8 +41,8 @@ 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.console.agent.handlers.DatabaseListener; +import org.apache.ignite.console.agent.handlers.RestListener; import org.apache.ignite.internal.util.typedef.X; import org.apache.log4j.Logger; import org.json.JSONException; @@ -278,141 +278,138 @@ public class AgentLauncher { cfg.tokens(Arrays.asList(tokens.trim().split(","))); } - final RestHandler restHnd = new RestHandler(cfg); + URI uri = URI.create(cfg.serverUri()); - try { - restHnd.start(); + // Create proxy authenticator using passed properties. + switch (uri.getScheme()) { + case "http": + case "https": + final String username = System.getProperty(uri.getScheme() + ".proxyUsername"); + final char[] pwd = System.getProperty(uri.getScheme() + ".proxyPassword", "").toCharArray(); - URI uri = URI.create(cfg.serverUri()); + Authenticator.setDefault(new Authenticator() { + @Override protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, pwd); + } + }); - // Create proxy authenticator using passed properties. - switch (uri.getScheme()) { - case "http": - case "https": - final String username = System.getProperty(uri.getScheme() + ".proxyUsername"); - final char[] pwd = System.getProperty(uri.getScheme() + ".proxyPassword", "").toCharArray(); + break; - Authenticator.setDefault(new Authenticator() { - @Override protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, pwd); - } - }); - - break; + default: + // No-op. + } - default: - // No-op. - } + IO.Options opts = new IO.Options(); - IO.Options opts = new IO.Options(); + opts.path = "/agents"; - opts.path = "/agents"; + // Workaround for use self-signed certificate + if (Boolean.getBoolean("trust.all")) { + SSLContext ctx = SSLContext.getInstance("TLS"); - // 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); - // Create an SSLContext that uses our TrustManager - ctx.init(null, getTrustManagers(), null); + opts.sslContext = ctx; + } - opts.sslContext = ctx; - } + final Socket client = IO.socket(uri, opts); - final Socket client = IO.socket(uri, opts); + final RestListener restHnd = new RestListener(cfg); - try { - Emitter.Listener onConnecting = new Emitter.Listener() { - @Override public void call(Object... args) { - log.info("Connecting to: " + cfg.serverUri()); - } - }; + final DatabaseListener dbHnd = new DatabaseListener(cfg); - Emitter.Listener onConnect = new Emitter.Listener() { - @Override public void call(Object... args) { - log.info("Connection established."); + try { + Emitter.Listener onConnecting = new Emitter.Listener() { + @Override public void call(Object... args) { + log.info("Connecting to: " + cfg.serverUri()); + } + }; - JSONObject authMsg = new JSONObject(); + Emitter.Listener onConnect = new Emitter.Listener() { + @Override public void call(Object... args) { + log.info("Connection established."); - try { - authMsg.put("tokens", cfg.tokens()); + JSONObject authMsg = new JSONObject(); - String clsName = AgentLauncher.class.getSimpleName() + ".class"; + try { + authMsg.put("tokens", cfg.tokens()); - String clsPath = AgentLauncher.class.getResource(clsName).toString(); + String clsName = AgentLauncher.class.getSimpleName() + ".class"; - if (clsPath.startsWith("jar")) { - String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) + - "/META-INF/MANIFEST.MF"; + String clsPath = AgentLauncher.class.getResource(clsName).toString(); - Manifest manifest = new Manifest(new URL(manifestPath).openStream()); + if (clsPath.startsWith("jar")) { + String manifestPath = clsPath.substring(0, clsPath.lastIndexOf('!') + 1) + + "/META-INF/MANIFEST.MF"; - Attributes attr = manifest.getMainAttributes(); + Manifest manifest = new Manifest(new URL(manifestPath).openStream()); - authMsg.put("ver", attr.getValue("Implementation-Version")); - authMsg.put("bt", attr.getValue("Build-Time")); - } + Attributes attr = manifest.getMainAttributes(); - 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(args); + authMsg.put("ver", attr.getValue("Implementation-Version")); + authMsg.put("bt", attr.getValue("Build-Time")); + } - System.exit(1); - } + 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(args); - log.info("Authentication success."); + System.exit(1); } - }); - } - catch (JSONException | IOException e) { - log.error("Failed to construct authentication message", e); - client.close(); - } + log.info("Authentication success."); + } + }); } - }; - - 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_WARNING, new Emitter.Listener() { - @Override public void call(Object... args) { - log.warn(args[0]); - } - }) - .on(EVENT_AGENT_CLOSE, new Emitter.Listener() { - @Override public void call(Object... args) { - onDisconnect.call(args); + catch (JSONException | IOException e) { + log.error("Failed to construct authentication message", e); + + client.close(); + } + } + }; + + 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_WARNING, new Emitter.Listener() { + @Override public void call(Object... args) { + log.warn(args[0]); + } + }) + .on(EVENT_AGENT_CLOSE, new Emitter.Listener() { + @Override public void call(Object... args) { + onDisconnect.call(args); - client.off(); + client.off(); - latch.countDown(); - } - }); + latch.countDown(); + } + }); - client.connect(); + client.connect(); - latch.await(); - } - finally { - client.close(); - } + latch.await(); } finally { + client.close(); + restHnd.stop(); + + dbHnd.stop(); } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java index 50a849a..cb22651 100644 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/AgentUtils.java @@ -17,11 +17,19 @@ package org.apache.ignite.console.agent; +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 java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.security.ProtectionDomain; +import java.util.Arrays; import org.apache.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONObject; /** * Utility methods. @@ -30,6 +38,28 @@ public class AgentUtils { /** */ private static final Logger log = Logger.getLogger(AgentUtils.class.getName()); + /** JSON object mapper. */ + private static final ObjectMapper mapper = new ObjectMapper(); + + /** */ + private static final Ack NOOP_CB = new Ack() { + @Override public void call(Object... args) { + if (args != null && args.length > 0 && args[0] instanceof Throwable) + log.error("Failed to execute request on agent.", (Throwable) args[0]); + else + log.info("Request on agent successfully executed " + Arrays.toString(args)); + } + }; + + static { + JsonOrgModule module = new JsonOrgModule(); + + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); + mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + + mapper.registerModule(module); + } + /** * Default constructor. */ @@ -108,4 +138,54 @@ public class AgentUtils { return null; } + + /** + * Get callback from handler arguments. + * + * @param args Arguments. + * @return Callback or noop callback. + */ + public static Ack safeCallback(Object[] args) { + boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack; + + return hasCb ? (Ack)args[args.length - 1] : NOOP_CB; + } + + /** + * Remove callback from handler arguments. + * + * @param args Arguments. + * @return Arguments without callback. + */ + public static Object[] removeCallback(Object[] args) { + boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack; + + return hasCb ? Arrays.copyOf(args, args.length - 1) : args; + } + + /** + * Map java object to JSON object. + * + * @param obj Java object. + * @return {@link JSONObject} or {@link JSONArray}. + * @throws IllegalArgumentException If conversion fails due to incompatible type. + */ + public static Object toJSON(Object obj) { + if (obj instanceof Iterable) + return mapper.convertValue(obj, JSONArray.class); + + return mapper.convertValue(obj, JSONObject.class); + } + + /** + * Map JSON object to java object. + * + * @param obj {@link JSONObject} or {@link JSONArray}. + * @param toValType Expected value type. + * @return Mapped object type of {@link T}. + * @throws IllegalArgumentException If conversion fails due to incompatible type. + */ + public static <T> T fromJSON(Object obj, Class<T> toValType) throws IllegalArgumentException { + return mapper.convertValue(obj, toValType); + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.java deleted file mode 100644 index 7e4e320..0000000 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractHandler.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.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/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java new file mode 100644 index 0000000..987dac9 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/AbstractListener.java @@ -0,0 +1,104 @@ +/* + * 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.client.Ack; +import io.socket.emitter.Emitter; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.log4j.Logger; + +import static org.apache.ignite.console.agent.AgentUtils.removeCallback; +import static org.apache.ignite.console.agent.AgentUtils.fromJSON; +import static org.apache.ignite.console.agent.AgentUtils.safeCallback; +import static org.apache.ignite.console.agent.AgentUtils.toJSON; + +/** + * Base class for web socket handlers. + */ +abstract class AbstractListener implements Emitter.Listener { + /** */ + private ExecutorService pool; + + /** */ + final Logger log = Logger.getLogger(this.getClass().getName()); + + /** {@inheritDoc} */ + @SuppressWarnings("unchecked") + @Override public final void call(Object... args) { + final Ack cb = safeCallback(args); + + args = removeCallback(args); + + try { + final Map<String, Object> params; + + if (args == null || args.length == 0) + params = Collections.emptyMap(); + else if (args.length == 1) + params = fromJSON(args[0], Map.class); + else + throw new IllegalArgumentException("Wrong arguments count, must be <= 1: " + Arrays.toString(args)); + + if (pool == null) + pool = newThreadPool(); + + pool.submit(new Runnable() { + @Override public void run() { + try { + Object res = execute(params); + + cb.call(null, toJSON(res)); + } catch (Exception e) { + cb.call(e, null); + } + } + }); + } + catch (Exception e) { + cb.call(e, null); + } + } + + /** + * Stop handler. + */ + public void stop() { + if (pool != null) + pool.shutdownNow(); + } + + /** + * Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically. + * + * @return Newly created thread pool. + */ + protected ExecutorService newThreadPool() { + return Executors.newSingleThreadExecutor(); + } + + /** + * 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/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java deleted file mode 100644 index 02146d9..0000000 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseHandler.java +++ /dev/null @@ -1,298 +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 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/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java new file mode 100644 index 0000000..4577228 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/DatabaseListener.java @@ -0,0 +1,316 @@ +/* + * 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 DatabaseListener { + /** */ + private static final Logger log = Logger.getLogger(DatabaseListener.class.getName()); + + /** */ + private final File driversFolder; + + /** */ + private final AbstractListener schemasLsnr = new AbstractListener() { + @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); + } + }; + + private final AbstractListener metadataLsnr = new AbstractListener() { + @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); + } + }; + + private final AbstractListener availableDriversLsnr = new AbstractListener() { + @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; + } + }; + + /** + * @param cfg Config. + */ + public DatabaseListener(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 drivers. + * + * @return Drivers in drivers folder + * @see AgentConfiguration#driversFolder + */ + public Emitter.Listener availableDriversListener() { + return availableDriversLsnr; + } + + /** + * Listener for schema names. + * + * @return Collection of schema names. + */ + public Emitter.Listener schemasListener() { + return schemasLsnr; + } + + /** + * @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 metadataLsnr; + } + + /** + * Stop handler. + */ + public void stop() { + availableDriversLsnr.stop(); + + schemasLsnr.stop(); + + metadataLsnr.stop(); + } + + /** + * 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/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java deleted file mode 100644 index 1b4b565..0000000 --- a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestHandler.java +++ /dev/null @@ -1,276 +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.demo.AgentClusterDemo; -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. - AgentClusterDemo.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()) { - if (entry.getValue() != null) - builder.addParameter(entry.getKey(), 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/262a3410/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java ---------------------------------------------------------------------- diff --git a/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java new file mode 100644 index 0000000..1e86549 --- /dev/null +++ b/modules/web-console/web-agent/src/main/java/org/apache/ignite/console/agent/handlers/RestListener.java @@ -0,0 +1,280 @@ +/* + * 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 java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +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.AgentClusterDemo; +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 RestListener extends AbstractListener { + /** */ + private static final Logger log = Logger.getLogger(RestListener.class.getName()); + + /** */ + private final AgentConfiguration cfg; + + /** */ + private CloseableHttpClient httpClient; + + /** + * @param cfg Config. + */ + public RestListener(AgentConfiguration cfg) { + super(); + + this.cfg = cfg; + + httpClient = HttpClientBuilder.create().build(); + } + + /** {@inheritDoc} */ + @Override public void stop() { + super.stop(); + + if (httpClient != null) { + try { + httpClient.close(); + } + catch (IOException ignore) { + // No-op. + } + } + } + + /** {@inheritDoc} */ + @Override protected ExecutorService newThreadPool() { + return Executors.newCachedThreadPool(); + } + + /** {@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. + AgentClusterDemo.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()) { + if (entry.getValue() != null) + builder.addParameter(entry.getKey(), 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); + } + } +}