/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.test.platformsplugin.model; import java.net.URL; import java.net.URLClassLoader; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.Driver; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import junit.framework.TestCase; import org.eclipse.persistence.tools.workbench.platformsmodel.DatabasePlatform; import org.eclipse.persistence.tools.workbench.platformsmodel.DatabasePlatformRepository; import org.eclipse.persistence.tools.workbench.platformsmodel.DatabaseType; import org.eclipse.persistence.tools.workbench.test.utility.TestTools; import org.eclipse.persistence.tools.workbench.utility.io.FileTools; /** * These aren't tests so much as they are documentation of what comes * back from metadata calls to the various databases (Oracle, DB2, Sybase, etc.). */ public abstract class PlatformTests extends TestCase { private DatabasePlatformRepository databasePlatformRepository; private Driver driver; protected Connection connection; protected static final String CR = System.getProperty("line.separator"); protected PlatformTests(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); this.databasePlatformRepository = DatabasePlatformRepository.getDefault(); ClassLoader classLoader = new URLClassLoader(this.classpathEntries()); Class driverClass = Class.forName(this.driverClassName(), true, classLoader); this.driver = (Driver) driverClass.newInstance(); this.connection = this.buildConnection(); } protected abstract String driverClassName(); /** * Return the classpath entries required for the JDBC driver. */ protected URL[] classpathEntries() throws Exception { List urls = new ArrayList(); this.addClasspathEntriesTo(urls); return (URL[]) urls.toArray(new URL[urls.size()]); } /** * Add the URLs needed by a URL class loader to load the JDBC driver * to the specified list. */ protected void addClasspathEntriesTo(List urls) throws Exception { String[] driverJARNames = this.driverJARNames(); for (int i = 0; i < driverJARNames.length; i++) { urls.add(FileTools.resourceFile("/" + driverJARNames[i]).toURL()); } } /** * Return the names of the JARs needed to load the JDBC driver. * The JARs must be located in a directory on the classpath. * The JARs themselves should *not* be on the classpath. */ protected String[] driverJARNames() { return new String[0]; } /** * Build the driver directly; skip the DriverManager. */ protected Connection buildConnection() throws Exception { Properties props = new Properties(); String userName = this.userName(); if (userName != null) { props.put("user", userName); } String password = this.password(); if (password != null) { props.put("password", password); } return this.driver.connect(this.connectionURL(), props); } protected abstract String connectionURL(); protected String userName() { return this.defautlUserName(); } protected String defautlUserName() { return System.getProperty("user.name"); } protected String password() { return "password"; } protected void tearDown() throws Exception { this.connection.close(); TestTools.clear(this); super.tearDown(); } /** * build a table and verify what the metadata says about it afterwards */ public void testTableTypes() throws Exception { // dump all the type information for the current platform to the console // this.dumpDriverInformation(this.connection.getMetaData()); // this.dumpMetaDataMap(this.buildMetaDataMap(this.connection.getMetaData().getTypeInfo(), "TYPE_NAME")); this.verifyVersionNumber(); this.dropDatabaseObjects(); this.createDatabaseObjects(); Map metaDataMap = this.buildMetaDataMap(this.connection.getMetaData().getColumns(null, this.schemaPattern(), this.tableName(), null), "COLUMN_NAME"); // dump all the information about the table to the console // this.dumpMetaDataMap(metaDataMap); this.verifyTable(metaDataMap); this.verifyDatabasePlatformRepository(metaDataMap); } /** * Verify that we are running the test against the expected db server. * If this assertion fails, the server version has changed; which means we are either * running against the wrong server or we need to tweak the version number * returned by #expectedVersionNumber(). ~bjv */ protected void verifyVersionNumber() throws Exception { String actualVersionNumber = this.connection.getMetaData().getDatabaseProductVersion(); String expectedVersionNumber = this.expectedVersionNumber(); String errorMessage = "Expected version number: " + expectedVersionNumber + " but the actual version number was: " + actualVersionNumber; assertTrue(errorMessage, actualVersionNumber.indexOf(expectedVersionNumber) != -1); } protected String expectedVersionNumber() { throw new UnsupportedOperationException(); } protected String schemaPattern() { return this.userName().toUpperCase(); } protected void dumpDriverInformation(DatabaseMetaData dbMetaData) throws Exception { System.out.println("URL: " + this.connectionURL()); System.out.println("Database Product Name: " + dbMetaData.getDatabaseProductName()); System.out.println("Database Product Version: " + dbMetaData.getDatabaseProductVersion()); System.out.println("Driver Name: " + dbMetaData.getDriverName()); System.out.println("Driver Version: " + dbMetaData.getDriverVersion()); System.out.println(); } protected String tableName() { return this.baseName() + "_TABLE"; } protected String baseName() { return "FOO"; } protected void dropDatabaseObjects() throws Exception { Statement stmt = this.connection.createStatement(); try { stmt.executeUpdate(this.buildDropTableDDL()); } catch (SQLException ex) { // ex.printStackTrace(); // dump to console, but otherwise ignore } stmt.close(); } protected String buildDropTableDDL() { return "DROP TABLE " + this.tableName(); } protected void createDatabaseObjects() throws Exception { Statement stmt = this.connection.createStatement(); String createTableDDL = this.buildCreateTableDDL(); // dump the DDL to the console // System.out.println(createTableDDL); stmt.executeUpdate(createTableDDL); stmt.close(); } protected abstract void verifyTable(Map metaDataMap); protected void verifyColumnAttribute(Map metaDataMap, String columnName, String attributeName, int expected) { Map columnAttributes = (Map) metaDataMap.get(columnName); Number columnAttribute = (Number) columnAttributes.get(attributeName); // Oracle returns BigDecimal assertEquals(columnName + ":" + attributeName, expected, columnAttribute.intValue()); } protected void verifyColumnAttribute(Map metaDataMap, String columnName, String attributeName, Object expected) { Map columnAttributes = (Map) metaDataMap.get(columnName); Object columnAttribute = columnAttributes.get(attributeName); assertEquals(columnName + ":" + attributeName, expected, columnAttribute); } /** * build and return a map of maps; the first key is the value in the specified column name, * the second key is the metadata column name * e.g. NUMBER_COL => (TABLE_NAME => FOO; COLUMN_SIZE => 10; etc.) */ protected Map buildMetaDataMap(ResultSet rs, String keyColumnName) throws Exception { ResultSetMetaData rsMetaData = rs.getMetaData(); // this.dumpMetaData(rsMetaData); Map metaDataMap = new LinkedHashMap(); // maintain the map's order int columnCount = rsMetaData.getColumnCount(); while (rs.next()) { Object keyColumnValue = null; Map row = new LinkedHashMap(columnCount); // maintain the map's order for (int i = 1; i <= columnCount; i++) { String columnName = rsMetaData.getColumnName(i); Object columnValue = rs.getObject(i); row.put(columnName, columnValue); if (columnName.equals(keyColumnName)) { keyColumnValue = columnValue; } } metaDataMap.put(keyColumnValue, row); } rs.close(); return metaDataMap; } /** * dump the specified metadata map to the console * @see #buildMetaDataMap(ResultSet, String) */ protected void dumpMetaDataMap(Map map) { DatabasePlatformRepository repository = new DatabasePlatformRepository("foo"); for (Iterator rows = map.values().iterator(); rows.hasNext(); ) { Map row = (Map) rows.next(); for (Iterator columns = row.keySet().iterator(); columns.hasNext(); ) { String columnName = (String) columns.next(); System.out.print(columnName + ": " + row.get(columnName)); if (columnName.equals("DATA_TYPE")) { int code = ((Number) row.get(columnName)).intValue(); System.out.print(" - "); try { System.out.print(repository.getJDBCTypeRepository().jdbcTypeForCode(code).getName()); } catch (IllegalArgumentException ex) { System.out.print("*** UNKNOWN JDBC TYPE ***"); } } System.out.println(); } System.out.println(); } } protected void dumpMetaData(ResultSetMetaData rsMetaData) throws Exception { System.out.println("Database Metadata Result Set Columns"); System.out.println("===================================="); int columnCount = rsMetaData.getColumnCount(); for (int i = 1; i <= columnCount; i++) { System.out.println("column: " + i); System.out.println("catalog name: " + rsMetaData.getCatalogName(i)); System.out.println("schema name: " + rsMetaData.getSchemaName(i)); System.out.println("table name: " + rsMetaData.getTableName(i)); System.out.println("column name: " + rsMetaData.getColumnName(i)); System.out.println("type: " + rsMetaData.getColumnType(i)); System.out.println("type name: " + rsMetaData.getColumnTypeName(i)); System.out.println("class name: " + rsMetaData.getColumnClassName(i)); System.out.println("label: " + rsMetaData.getColumnLabel(i)); System.out.println("display size: " + rsMetaData.getColumnDisplaySize(i)); System.out.println("precision: " + rsMetaData.getPrecision(i)); System.out.println("scale: " + rsMetaData.getScale(i)); System.out.println("auto-increment: " + rsMetaData.isAutoIncrement(i)); System.out.println("case-sensitive: " + rsMetaData.isCaseSensitive(i)); System.out.println("currency: " + rsMetaData.isCurrency(i)); System.out.println("nullable: " + rsMetaData.isNullable(i)); System.out.println("searchable: " + rsMetaData.isSearchable(i)); System.out.println("read-only: " + rsMetaData.isReadOnly(i)); System.out.println("writable: " + rsMetaData.isWritable(i)); System.out.println("definitely writable: " + rsMetaData.isDefinitelyWritable(i)); System.out.println("signed: " + rsMetaData.isSigned(i)); System.out.println(); } } protected String buildCreateTableDDL() { StringBuffer sb = new StringBuffer(2000); sb.append("CREATE TABLE "); sb.append(this.tableName()); sb.append(" ("); sb.append(CR); this.appendColumnsToTableDDL(sb); sb.append(")"); return sb.toString(); } protected abstract void appendColumnsToTableDDL(StringBuffer sb); protected void verifyDatabasePlatformRepository(Map metaDataMap) { DatabasePlatform platform = this.databasePlatformRepository.platformNamed(this.platformName()); Object catalog = null; Object schema = null; Object table = null; for (Iterator stream = metaDataMap.keySet().iterator(); stream.hasNext(); ) { Object columnName = stream.next(); Map columnAttributes = (Map) metaDataMap.get(columnName); // make sure the catalog/schema/table remain the same if (catalog == null) catalog = columnAttributes.get("TABLE_CAT"); if (schema == null) schema = columnAttributes.get("TABLE_SCHEM"); if (table == null) table = columnAttributes.get("TABLE_NAME"); assertEquals(catalog, columnAttributes.get("TABLE_CAT")); assertEquals(schema, columnAttributes.get("TABLE_SCHEM")); assertEquals(table, columnAttributes.get("TABLE_NAME")); String nativeTypeName = (String) columnAttributes.get("TYPE_NAME"); DatabaseType type = null; try { type = platform.databaseTypeNamed(nativeTypeName); } catch (IllegalArgumentException ex) { if (ex.getMessage().indexOf(nativeTypeName) == -1) { throw ex; } if (this.typeIsIgnorable(nativeTypeName)) { // System.out.println("ignored data type: " + this.platformName() + ":" + nativeTypeName); type = platform.defaultDatabaseType(); } else { throw ex; } } assertNotNull(type); if (type.allowsSize()) { int size = ((Number) columnAttributes.get("COLUMN_SIZE")).intValue(); assertTrue(size >= 0); if (type.requiresSize()) { assertTrue(size > 0); } if (type.allowsSubSize()) { Number ss = (Number) columnAttributes.get("DECIMAL_DIGITS"); int subSize = (ss == null) ? 0 : ss.intValue(); assertTrue(subSize >= 0); } } if (type.allowsNull()) { assertEquals("YES", columnAttributes.get("IS_NULLABLE")); } } } protected abstract String platformName(); /** * return whether we can ignore the specified datatype * when trying to look it up in the repository; * i.e. we will not find the datatype in the database platform * repository because it is a "user-defined" datatype (e.g. * FOO_TYPE) not a "system" datatype (e.g. VARCHAR2) */ protected boolean typeIsIgnorable(String nativeTypeName) { Collection adtNames = new ArrayList(); this.addIgnorableTypeNamesTo(adtNames); return adtNames.contains(nativeTypeName); } protected void addIgnorableTypeNamesTo(Collection adtNames) { // none by default } }