franta-hg@16: /** franta-hg@16: * SQL-DK franta-hg@16: * Copyright © 2013 František Kučera (frantovo.cz) franta-hg@16: * franta-hg@16: * This program is free software: you can redistribute it and/or modify franta-hg@16: * it under the terms of the GNU General Public License as published by franta-hg@16: * the Free Software Foundation, either version 3 of the License, or franta-hg@16: * (at your option) any later version. franta-hg@16: * franta-hg@16: * This program is distributed in the hope that it will be useful, franta-hg@16: * but WITHOUT ANY WARRANTY; without even the implied warranty of franta-hg@16: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the franta-hg@16: * GNU General Public License for more details. franta-hg@16: * franta-hg@16: * You should have received a copy of the GNU General Public License franta-hg@16: * along with this program. If not, see . franta-hg@16: */ franta-hg@14: package info.globalcode.sql.dk; franta-hg@14: franta-hg@67: import info.globalcode.sql.dk.configuration.Configuration; franta-hg@65: import info.globalcode.sql.dk.configuration.ConfigurationException; franta-hg@26: import info.globalcode.sql.dk.configuration.ConfigurationProvider; franta-hg@65: import info.globalcode.sql.dk.configuration.DatabaseDefinition; franta-hg@67: import info.globalcode.sql.dk.configuration.FormatterDefinition; franta-hg@160: import info.globalcode.sql.dk.configuration.Properties; franta-hg@159: import info.globalcode.sql.dk.configuration.Property; franta-hg@69: import info.globalcode.sql.dk.formatting.ColumnsHeader; franta-hg@159: import info.globalcode.sql.dk.formatting.FakeSqlArray; franta-hg@69: import info.globalcode.sql.dk.formatting.Formatter; franta-hg@69: import info.globalcode.sql.dk.formatting.FormatterContext; franta-hg@69: import info.globalcode.sql.dk.formatting.FormatterException; franta-hg@17: import java.io.BufferedReader; franta-hg@160: import java.io.ByteArrayOutputStream; franta-hg@17: import java.io.InputStreamReader; franta-hg@14: import java.io.PrintStream; franta-hg@159: import java.sql.Array; franta-hg@158: import java.sql.Driver; franta-hg@159: import java.sql.DriverPropertyInfo; franta-hg@65: import java.sql.SQLException; franta-hg@70: import java.util.ArrayList; franta-hg@70: import java.util.EnumSet; franta-hg@159: import java.util.HashSet; franta-hg@66: import java.util.List; franta-hg@158: import java.util.ServiceLoader; franta-hg@159: import java.util.Set; franta-hg@17: import java.util.logging.Level; franta-hg@17: import java.util.logging.Logger; franta-hg@69: import javax.sql.rowset.RowSetMetaDataImpl; franta-hg@14: franta-hg@14: /** franta-hg@14: * Displays info like help, version etc. franta-hg@14: * franta-hg@14: * @author Ing. František Kučera (frantovo.cz) franta-hg@14: */ franta-hg@14: public class InfoLister { franta-hg@14: franta-hg@17: private static final Logger log = Logger.getLogger(InfoLister.class.getName()); franta-hg@159: /** franta-hg@159: * Fake database name for output formatting franta-hg@159: */ franta-hg@159: public static final String CONFIG_DB_NAME = "sqldk_configuration"; franta-hg@17: private PrintStream out; franta-hg@20: private ConfigurationProvider configurationProvider; franta-hg@69: private CLIOptions options; franta-hg@70: private Formatter formatter; franta-hg@17: franta-hg@69: public InfoLister(PrintStream out, ConfigurationProvider configurationProvider, CLIOptions options) { franta-hg@17: this.out = out; franta-hg@20: this.configurationProvider = configurationProvider; franta-hg@69: this.options = options; franta-hg@17: } franta-hg@17: franta-hg@70: public void showInfo() throws ConfigurationException, FormatterException { franta-hg@70: EnumSet commands = options.getShowInfo(); franta-hg@70: franta-hg@139: boolean formattinNeeded = false; franta-hg@139: franta-hg@70: for (InfoType infoType : commands) { franta-hg@70: switch (infoType) { franta-hg@70: case CONNECTION: franta-hg@158: case JDBC_DRIVERS: franta-hg@159: case JDBC_PROPERTIES: franta-hg@70: case DATABASES: franta-hg@70: case FORMATTERS: franta-hg@70: case TYPES: franta-hg@139: formattinNeeded = true; franta-hg@101: break; franta-hg@70: } franta-hg@70: } franta-hg@139: franta-hg@139: if (formattinNeeded) { franta-hg@139: try (Formatter f = getFormatter()) { franta-hg@139: formatter = f; franta-hg@139: formatter.writeStartBatch(); franta-hg@159: DatabaseDefinition dd = new DatabaseDefinition(); franta-hg@159: dd.setName(CONFIG_DB_NAME); franta-hg@159: formatter.writeStartDatabase(dd); franta-hg@139: showInfos(commands); franta-hg@139: formatter.writeEndDatabase(); franta-hg@139: formatter.writeEndBatch(); franta-hg@139: formatter.close(); franta-hg@139: } franta-hg@139: } else { franta-hg@139: showInfos(commands); franta-hg@139: } franta-hg@101: } franta-hg@70: franta-hg@101: private void showInfos(EnumSet commands) throws ConfigurationException, FormatterException { franta-hg@70: for (InfoType infoType : commands) { franta-hg@70: infoType.showInfo(this); franta-hg@70: } franta-hg@70: } franta-hg@70: franta-hg@72: private void listFormatters() throws ConfigurationException, FormatterException { franta-hg@72: ColumnsHeader header = constructHeader( franta-hg@72: new HeaderField("name", SQLType.VARCHAR), franta-hg@72: new HeaderField("built_in", SQLType.BOOLEAN), franta-hg@72: new HeaderField("default", SQLType.BOOLEAN), franta-hg@160: new HeaderField("class_name", SQLType.VARCHAR), franta-hg@160: new HeaderField("valid", SQLType.BOOLEAN)); franta-hg@72: List data = new ArrayList<>(); franta-hg@72: franta-hg@72: String defaultFormatter = configurationProvider.getConfiguration().getDefaultFormatter(); franta-hg@72: defaultFormatter = defaultFormatter == null ? Configuration.DEFAULT_FORMATTER : defaultFormatter; franta-hg@72: franta-hg@69: for (FormatterDefinition fd : configurationProvider.getConfiguration().getBuildInFormatters()) { franta-hg@160: data.add(new Object[]{fd.getName(), true, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)}); franta-hg@69: } franta-hg@72: franta-hg@72: for (FormatterDefinition fd : configurationProvider.getConfiguration().getFormatters()) { franta-hg@160: data.add(new Object[]{fd.getName(), false, defaultFormatter.equals(fd.getName()), fd.getClassName(), isInstantiable(fd)}); franta-hg@69: } franta-hg@72: franta-hg@159: printTable(formatter, header, data, "-- configured and built-in output formatters", null); franta-hg@160: } franta-hg@72: franta-hg@160: private boolean isInstantiable(FormatterDefinition fd) { franta-hg@160: try { franta-hg@160: try (ByteArrayOutputStream testStream = new ByteArrayOutputStream()) { franta-hg@160: fd.getInstance(new FormatterContext(testStream, new Properties(0))); franta-hg@160: return true; franta-hg@160: } franta-hg@160: } catch (Exception e) { franta-hg@160: log.log(Level.SEVERE, "Unable to create an instance of formatter: " + fd.getName(), e); franta-hg@160: return false; franta-hg@160: } franta-hg@69: } franta-hg@67: franta-hg@70: public void listTypes() throws FormatterException, ConfigurationException { franta-hg@70: ColumnsHeader header = constructHeader(new HeaderField("name", SQLType.VARCHAR), new HeaderField("code", SQLType.INTEGER)); franta-hg@70: List data = new ArrayList<>(); franta-hg@70: for (SQLType sqlType : SQLType.values()) { franta-hg@70: data.add(new Object[]{sqlType.name(), sqlType.getCode()}); franta-hg@70: } franta-hg@159: printTable(formatter, header, data, "-- data types", null); franta-hg@93: log.log(Level.INFO, "Type names in --types option are case insensitive"); franta-hg@69: } franta-hg@67: franta-hg@72: public void listDatabases() throws ConfigurationException, FormatterException { franta-hg@72: ColumnsHeader header = constructHeader( franta-hg@72: new HeaderField("database_name", SQLType.VARCHAR), franta-hg@72: new HeaderField("user_name", SQLType.VARCHAR), franta-hg@72: new HeaderField("database_url", SQLType.VARCHAR)); franta-hg@72: List data = new ArrayList<>(); franta-hg@72: franta-hg@69: final List configuredDatabases = configurationProvider.getConfiguration().getDatabases(); franta-hg@69: if (configuredDatabases.isEmpty()) { franta-hg@69: log.log(Level.WARNING, "No databases are configured."); franta-hg@69: } else { franta-hg@69: for (DatabaseDefinition dd : configuredDatabases) { franta-hg@72: data.add(new Object[]{dd.getName(), dd.getUserName(), dd.getUrl()}); franta-hg@14: } franta-hg@14: } franta-hg@72: franta-hg@159: printTable(formatter, header, data, "-- configured databases", null); franta-hg@14: } franta-hg@17: franta-hg@158: public void listJdbcDrivers() throws FormatterException, ConfigurationException { franta-hg@158: ColumnsHeader header = constructHeader( franta-hg@158: new HeaderField("class", SQLType.VARCHAR), franta-hg@158: new HeaderField("version", SQLType.VARCHAR), franta-hg@158: new HeaderField("major", SQLType.INTEGER), franta-hg@158: new HeaderField("minor", SQLType.INTEGER), franta-hg@158: new HeaderField("jdbc_compliant", SQLType.BOOLEAN)); franta-hg@158: List data = new ArrayList<>(); franta-hg@158: franta-hg@158: final ServiceLoader drivers = ServiceLoader.load(Driver.class); franta-hg@158: for (Driver d : drivers) { franta-hg@158: data.add(new Object[]{ franta-hg@158: d.getClass().getName(), franta-hg@158: d.getMajorVersion() + "." + d.getMinorVersion(), franta-hg@158: d.getMajorVersion(), franta-hg@158: d.getMinorVersion(), franta-hg@159: d.jdbcCompliant() franta-hg@159: }); franta-hg@158: } franta-hg@158: franta-hg@159: printTable(formatter, header, data, "-- discovered JDBC drivers (available on the CLASSPATH)", null); franta-hg@159: } franta-hg@158: franta-hg@159: public void listJdbcProperties() throws FormatterException, ConfigurationException { franta-hg@159: for (String dbName : options.getDatabaseNamesToListProperties()) { franta-hg@159: ColumnsHeader header = constructHeader( franta-hg@159: new HeaderField("property_name", SQLType.VARCHAR), franta-hg@159: new HeaderField("required", SQLType.BOOLEAN), franta-hg@159: new HeaderField("choices", SQLType.ARRAY), franta-hg@159: new HeaderField("configured_value", SQLType.VARCHAR), franta-hg@159: new HeaderField("description", SQLType.VARCHAR)); franta-hg@159: List data = new ArrayList<>(); franta-hg@159: franta-hg@159: DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName); franta-hg@159: franta-hg@159: Driver driver = findDriver(dd); franta-hg@159: franta-hg@159: if (driver == null) { franta-hg@159: log.log(Level.WARNING, "No JDBC driver was found for DB: {0} with URL: {1}", new Object[]{dd.getName(), dd.getUrl()}); franta-hg@159: } else { franta-hg@159: log.log(Level.INFO, "For DB: {0} was found JDBC driver: {1}", new Object[]{dd.getName(), driver.getClass().getName()}); franta-hg@159: franta-hg@159: try { franta-hg@159: DriverPropertyInfo[] propertyInfos = driver.getPropertyInfo(dd.getUrl(), dd.getProperties().getJavaProperties()); franta-hg@159: franta-hg@159: Set standardProperties = new HashSet<>(); franta-hg@159: franta-hg@159: for (DriverPropertyInfo pi : propertyInfos) { franta-hg@159: Array choices = new FakeSqlArray(pi.choices, SQLType.VARCHAR); franta-hg@159: data.add(new Object[]{ franta-hg@159: pi.name, franta-hg@159: pi.required, franta-hg@159: choices.getArray() == null ? "" : choices, franta-hg@159: pi.value == null ? "" : pi.value, franta-hg@159: pi.description franta-hg@159: }); franta-hg@159: standardProperties.add(pi.name); franta-hg@159: } franta-hg@159: franta-hg@159: for (Property p : dd.getProperties()) { franta-hg@159: if (!standardProperties.contains(p.getName())) { franta-hg@159: data.add(new Object[]{ franta-hg@159: p.getName(), franta-hg@159: "", franta-hg@159: "", franta-hg@159: p.getValue(), franta-hg@159: "" franta-hg@159: }); franta-hg@159: log.log(Level.WARNING, "Your configuration contains property „{0}“ not declared by the JDBC driver.", p.getName()); franta-hg@159: } franta-hg@159: } franta-hg@159: franta-hg@159: } catch (SQLException e) { franta-hg@159: log.log(Level.WARNING, "Error during getting property infos.", e); franta-hg@159: } franta-hg@159: franta-hg@159: List parameters = new ArrayList<>(); franta-hg@159: parameters.add(new NamedParameter("databgase", dbName, SQLType.VARCHAR)); franta-hg@159: parameters.add(new NamedParameter("driver_class", driver.getClass().getName(), SQLType.VARCHAR)); franta-hg@159: parameters.add(new NamedParameter("driver_major_version", driver.getMajorVersion(), SQLType.INTEGER)); franta-hg@159: parameters.add(new NamedParameter("driver_minor_version", driver.getMinorVersion(), SQLType.INTEGER)); franta-hg@159: franta-hg@159: printTable(formatter, header, data, "-- configured and configurable JDBC driver properties", parameters); franta-hg@159: } franta-hg@159: } franta-hg@159: franta-hg@159: } franta-hg@159: franta-hg@159: private Driver findDriver(DatabaseDefinition dd) { franta-hg@159: final ServiceLoader drivers = ServiceLoader.load(Driver.class); franta-hg@159: for (Driver d : drivers) { franta-hg@159: try { franta-hg@159: if (d.acceptsURL(dd.getUrl())) { franta-hg@159: return d; franta-hg@159: } franta-hg@159: } catch (SQLException e) { franta-hg@159: log.log(Level.WARNING, "Error during finding JDBC driver for: " + dd.getName(), e); franta-hg@159: } franta-hg@159: } franta-hg@159: return null; franta-hg@158: } franta-hg@158: franta-hg@73: public void testConnection() throws FormatterException, ConfigurationException { franta-hg@73: ColumnsHeader header = constructHeader( franta-hg@73: new HeaderField("database_name", SQLType.VARCHAR), franta-hg@74: new HeaderField("configured", SQLType.BOOLEAN), franta-hg@74: new HeaderField("connected", SQLType.BOOLEAN)); franta-hg@73: List data = new ArrayList<>(); franta-hg@73: franta-hg@155: for (String dbName : options.getDatabaseNamesToTest()) { franta-hg@74: data.add(testConnection(dbName)); franta-hg@74: } franta-hg@73: franta-hg@159: printTable(formatter, header, data, "-- database configuration and connectivity test", null); franta-hg@73: } franta-hg@73: franta-hg@73: public Object[] testConnection(String dbName) { franta-hg@69: log.log(Level.FINE, "Testing connection to database: {0}", dbName); franta-hg@73: franta-hg@73: boolean succesfullyConnected = false; franta-hg@73: boolean succesfullyConfigured = false; franta-hg@73: franta-hg@69: try { franta-hg@69: DatabaseDefinition dd = configurationProvider.getConfiguration().getDatabase(dbName); franta-hg@75: log.log(Level.FINE, "Database definition was loaded from configuration"); franta-hg@75: succesfullyConfigured = true; franta-hg@106: try (DatabaseConnection dc = dd.connect(options.getDatabaseProperties())) { franta-hg@75: succesfullyConnected = dc.test(); franta-hg@69: } franta-hg@75: log.log(Level.FINE, "Database connection test was successful"); franta-hg@69: } catch (ConfigurationException | SQLException e) { franta-hg@69: log.log(Level.SEVERE, "Error during testing connection", e); franta-hg@69: } franta-hg@73: franta-hg@73: return new Object[]{dbName, succesfullyConfigured, succesfullyConnected}; franta-hg@69: } franta-hg@69: franta-hg@69: public void printResource(String fileName) { franta-hg@18: try (BufferedReader reader = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(fileName)))) { franta-hg@17: while (true) { franta-hg@18: String line = reader.readLine(); franta-hg@17: if (line == null) { franta-hg@17: break; franta-hg@17: } else { franta-hg@17: println(line); franta-hg@17: } franta-hg@17: } franta-hg@17: } catch (Exception e) { franta-hg@18: log.log(Level.SEVERE, "Unable to print this info. Please see our website for it: " + Constants.WEBSITE, e); franta-hg@17: } franta-hg@17: } franta-hg@17: franta-hg@17: private void println(String line) { franta-hg@17: out.println(line); franta-hg@17: } franta-hg@69: franta-hg@159: private void printTable(Formatter formatter, ColumnsHeader header, List data, String sql, List parameters) throws ConfigurationException, FormatterException { franta-hg@159: formatter.writeStartStatement(); franta-hg@159: franta-hg@159: if (sql != null) { franta-hg@159: formatter.writeQuery(sql); franta-hg@159: if (parameters != null) { franta-hg@159: formatter.writeParameters(parameters); franta-hg@159: } franta-hg@159: } franta-hg@159: franta-hg@142: formatter.writeStartResultSet(header); franta-hg@69: franta-hg@69: for (Object[] row : data) { franta-hg@69: formatter.writeStartRow(); franta-hg@69: for (Object cell : row) { franta-hg@69: formatter.writeColumnValue(cell); franta-hg@69: } franta-hg@69: formatter.writeEndRow(); franta-hg@69: } franta-hg@69: franta-hg@69: formatter.writeEndResultSet(); franta-hg@159: formatter.writeEndStatement(); franta-hg@69: } franta-hg@69: franta-hg@69: private Formatter getFormatter() throws ConfigurationException, FormatterException { franta-hg@89: String formatterName = options.getFormatterName(); franta-hg@89: formatterName = formatterName == null ? Configuration.DEFAULT_FORMATTER_PREFETCHING : formatterName; franta-hg@89: FormatterDefinition fd = configurationProvider.getConfiguration().getFormatter(formatterName); franta-hg@104: FormatterContext context = new FormatterContext(out, options.getFormatterProperties()); franta-hg@69: return fd.getInstance(context); franta-hg@69: } franta-hg@69: franta-hg@69: private ColumnsHeader constructHeader(HeaderField... fields) throws FormatterException { franta-hg@69: try { franta-hg@69: RowSetMetaDataImpl metaData = new RowSetMetaDataImpl(); franta-hg@69: metaData.setColumnCount(fields.length); franta-hg@69: franta-hg@69: for (int i = 0; i < fields.length; i++) { franta-hg@69: HeaderField hf = fields[i]; franta-hg@69: int sqlIndex = i + 1; franta-hg@69: metaData.setColumnName(sqlIndex, hf.name); franta-hg@69: metaData.setColumnLabel(sqlIndex, hf.name); franta-hg@69: metaData.setColumnType(sqlIndex, hf.type.getCode()); franta-hg@69: metaData.setColumnTypeName(sqlIndex, hf.type.name()); franta-hg@69: } franta-hg@69: franta-hg@69: return new ColumnsHeader(metaData); franta-hg@69: } catch (SQLException e) { franta-hg@69: throw new FormatterException("Error while constructing table headers", e); franta-hg@69: } franta-hg@69: } franta-hg@69: franta-hg@69: private static class HeaderField { franta-hg@69: franta-hg@69: String name; franta-hg@69: SQLType type; franta-hg@69: franta-hg@69: public HeaderField(String name, SQLType type) { franta-hg@69: this.name = name; franta-hg@69: this.type = type; franta-hg@69: } franta-hg@69: } franta-hg@69: franta-hg@69: public enum InfoType { franta-hg@69: franta-hg@69: HELP { franta-hg@69: @Override franta-hg@69: public void showInfo(InfoLister infoLister) { franta-hg@69: infoLister.printResource(Constants.HELP_FILE); franta-hg@69: } franta-hg@69: }, franta-hg@69: VERSION { franta-hg@69: @Override franta-hg@69: public void showInfo(InfoLister infoLister) { franta-hg@69: infoLister.printResource(Constants.VERSION_FILE); franta-hg@69: } franta-hg@69: }, franta-hg@69: LICENSE { franta-hg@69: @Override franta-hg@69: public void showInfo(InfoLister infoLister) { franta-hg@69: infoLister.printResource(Constants.LICENSE_FILE); franta-hg@69: } franta-hg@69: }, franta-hg@69: FORMATTERS { franta-hg@69: @Override franta-hg@70: public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { franta-hg@69: infoLister.listFormatters(); franta-hg@69: } franta-hg@69: }, franta-hg@69: TYPES { franta-hg@69: @Override franta-hg@70: public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { franta-hg@69: infoLister.listTypes(); franta-hg@69: } franta-hg@69: }, franta-hg@158: JDBC_DRIVERS { franta-hg@158: @Override franta-hg@158: public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { franta-hg@158: infoLister.listJdbcDrivers(); franta-hg@158: } franta-hg@158: }, franta-hg@159: JDBC_PROPERTIES { franta-hg@159: @Override franta-hg@159: public void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException { franta-hg@159: infoLister.listJdbcProperties(); franta-hg@159: } franta-hg@159: }, franta-hg@69: DATABASES { franta-hg@69: @Override franta-hg@70: public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { franta-hg@69: infoLister.listDatabases(); franta-hg@69: } franta-hg@69: }, franta-hg@69: CONNECTION { franta-hg@69: @Override franta-hg@73: public void showInfo(InfoLister infoLister) throws FormatterException, ConfigurationException { franta-hg@69: infoLister.testConnection(); franta-hg@69: } franta-hg@69: }; franta-hg@69: franta-hg@69: public abstract void showInfo(InfoLister infoLister) throws ConfigurationException, FormatterException; franta-hg@69: } franta-hg@14: }