blob: d794297a3261f3c8d9c4af94e103e928d6f4e3b2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ignite.console.agent.handlers;
import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import io.socket.emitter.Emitter;
import org.apache.ignite.console.agent.AgentConfiguration;
import org.apache.ignite.console.agent.db.DbMetadataReader;
import org.apache.ignite.console.agent.db.DbSchema;
import org.apache.ignite.console.agent.db.DbTable;
import org.apache.ignite.console.demo.AgentMetadataDemo;
import org.apache.log4j.Logger;
import static java.nio.charset.StandardCharsets.UTF_8;
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 static final String IMPLEMENTATION_VERSION = "Implementation-Version";
/** */
private static final String BUNDLE_VERSION = "Bundle-Version";
/** */
private final File driversFolder;
/** */
private final DbMetadataReader dbMetaReader;
/** */
private final AbstractListener schemasLsnr = new AbstractListener() {
@Override public Object execute(Map<String, Object> args) throws Exception {
String driverPath = null;
if (args.containsKey("jdbcDriverJar"))
driverPath = args.get("jdbcDriverJar").toString();
if (!args.containsKey("jdbcDriverClass"))
throw new IllegalArgumentException("Missing driverClass in arguments: " + args);
String driverCls = args.get("jdbcDriverClass").toString();
if (!args.containsKey("jdbcUrl"))
throw new IllegalArgumentException("Missing url in arguments: " + args);
String url = args.get("jdbcUrl").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() {
@Override public Object execute(Map<String, Object> args) throws Exception {
String driverPath = null;
if (args.containsKey("jdbcDriverJar"))
driverPath = args.get("jdbcDriverJar").toString();
if (!args.containsKey("jdbcDriverClass"))
throw new IllegalArgumentException("Missing driverClass in arguments: " + args);
String driverCls = args.get("jdbcDriverClass").toString();
if (!args.containsKey("jdbcUrl"))
throw new IllegalArgumentException("Missing url in arguments: " + args);
String url = args.get("jdbcUrl").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) {
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(), UTF_8));
JarFile jar = new JarFile(file.getPath())
) {
Manifest m = jar.getManifest();
Object ver = m.getMainAttributes().getValue(IMPLEMENTATION_VERSION);
if (ver == null)
ver = m.getMainAttributes().getValue(BUNDLE_VERSION);
String jdbcDriverCls = reader.readLine();
res.add(new JdbcDriver(file.getName(), jdbcDriverCls, ver != null ? ver.toString() : null));
if (log.isDebugEnabled())
log.debug("Found: [driver=" + file + ", class=" + jdbcDriverCls + "]");
}
}
catch (IOException e) {
res.add(new JdbcDriver(file.getName(), null, 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());
dbMetaReader = new DbMetadataReader();
}
/**
* @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 If failed to connect.
*/
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 dbMetaReader.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 If failed to collect schemas.
*/
protected DbSchema 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)) {
String catalog = conn.getCatalog();
if (catalog == null) {
String[] parts = jdbcUrl.split("[/:=]");
catalog = parts.length > 0 ? parts[parts.length - 1] : "NONE";
}
Collection<String> schemas = dbMetaReader.schemas(conn);
if (log.isDebugEnabled())
log.debug("Finished collection of schemas [jdbcUrl=" + jdbcUrl + ", catalog=" + catalog +
", count=" + schemas.size() + "]");
return new DbSchema(catalog, 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 = dbMetaReader.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;
/** */
public final String jdbcDriverImplVersion;
/**
* @param jdbcDriverJar File name of driver jar file.
* @param jdbcDriverCls Optional JDBC driver class.
*/
public JdbcDriver(String jdbcDriverJar, String jdbcDriverCls, String jdbcDriverImplVersion) {
this.jdbcDriverJar = jdbcDriverJar;
this.jdbcDriverCls = jdbcDriverCls;
this.jdbcDriverImplVersion = jdbcDriverImplVersion;
}
}
}