/* * 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.openjpa.jdbc.sql; import java.security.AccessController; import java.security.PrivilegedActionException; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Locale; import java.util.Objects; import javax.sql.DataSource; import org.apache.openjpa.lib.util.StringUtil; import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; import org.apache.openjpa.lib.conf.Configurations; import org.apache.openjpa.lib.conf.PluginValue; import org.apache.openjpa.lib.log.Log; import org.apache.openjpa.lib.util.J2DoPrivHelper; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.util.StoreException; import org.apache.openjpa.util.UserException; /** * Factory class to instantiate a dictionary. It will use * the following heuristic: * <ul> * <li>Check to see if there is a DictionaryClass property, * and if so, use that to instantiate the dictionary.</li> * <li>Check the URL in the JDBCConfiguration against a list * of pre-defined URLs for various dictionaries.</li> * <li>Check the driver in the JDBCConfiguration against a list of known * patterns.</li> * <li>Acquire a connection and check its database metadata.</li> * <li>Return an instance of the generic DBDictionary.</li> * </ul> * * @author Marc Prud'hommeaux */ public class DBDictionaryFactory { private static final Localizer _loc = Localizer.forPackage (DBDictionaryFactory.class); /** * Create the dictionary for the given class name and properties. */ public static DBDictionary newDBDictionary(JDBCConfiguration conf, String dclass, String props) { return newDBDictionary(conf, dclass, props, null); } /** * Attempt to create the dictionary from the given connection URL and * driver name, either or both of which may be null. If the dictionary * cannot be calculated, returns null. */ public static DBDictionary calculateDBDictionary(JDBCConfiguration conf, String url, String driver, String props) { String dclass = dictionaryClassForString(getProtocol(url), conf); if (dclass == null) dclass = dictionaryClassForString(driver, conf); if (dclass == null) return null; return newDBDictionary(conf, dclass, props); } /** * Create the dictionary using connection metadata to determine its type. */ public static DBDictionary newDBDictionary(JDBCConfiguration conf, DataSource ds, String props) { Connection conn = null; try { conn = ds.getConnection(); DatabaseMetaData meta = conn.getMetaData(); String dclass = dictionaryClassForString(meta.getDatabaseProductName(), conf); if (dclass == null) dclass = dictionaryClassForString(getProtocol(meta.getURL()), conf); if (dclass != null && dclass.contains("MySQL")) { // MariaDB returns "MySQL" for product name, need to verify by looking at product version. final String checkMariaDB = dictionaryClassForString(meta.getDatabaseProductVersion(), conf); if (checkMariaDB != null) { dclass = checkMariaDB; } } if (dclass == null) dclass = DBDictionary.class.getName(); return newDBDictionary(conf, dclass, props, conn); } catch (SQLException se) { throw new StoreException(se).setFatal(true); } finally { if (conn != null) try { conn.close(); } catch (SQLException se) { } } } /* * Returns the "jdbc:" protocol of the url parameter. Looks for the prefix * string up to the 3rd ':' or the 1st '@', '/' or '\', whichever comes * first. * * This method is package qualified so that TestDictionaryFactory class can * access and test this method behavior. */ static String getProtocol(String url) { String protocol = null; if (!StringUtil.isEmpty(url)) { if (url.startsWith("jdbc:")) { int colonCount = 1; int next = "jdbc:".length(); int protoEnd = next; while (colonCount < 3 && next < url.length()) { char c = url.charAt(next++); if (c == ':') { ++colonCount; protoEnd = next; } else if (c == '@' || c == '/' || c == '\\') { --next; break; } } protocol = url.substring(0, protoEnd); } } return protocol; } /** * Create the dictionary using the given class name and properties; the * connection may be null if not supplied to the factory. */ private static DBDictionary newDBDictionary(JDBCConfiguration conf, String dclass, String props, Connection conn) { DBDictionary dict = null; try { Class<?> c = Class.forName(dclass, true, AccessController.doPrivileged( J2DoPrivHelper.getClassLoaderAction( DBDictionary.class))); dict = (DBDictionary) AccessController.doPrivileged( J2DoPrivHelper.newInstanceAction(c)); } catch (ClassNotFoundException cnfe) { // if the dictionary was not found, make another attempt // at loading the dictionary using the current thread. try { Class<?> c = Thread.currentThread().getContextClassLoader().loadClass(dclass); dict = (DBDictionary) AccessController.doPrivileged( J2DoPrivHelper.newInstanceAction(c)); } catch (Exception e) { if (e instanceof PrivilegedActionException) e = ((PrivilegedActionException) e).getException(); throw new UserException(e).setFatal(true); } } catch (Exception e) { if (e instanceof PrivilegedActionException) e = ((PrivilegedActionException) e).getException(); throw new UserException(e).setFatal(true); } // warn if we could not locate the appropriate dictionary Log log = conf.getLog(JDBCConfiguration.LOG_JDBC); if (log.isWarnEnabled() && dict.getClass() == DBDictionary.class) log.warn(_loc.get("warn-generic")); if (log.isInfoEnabled()) { String infoString = ""; if (conn != null) { try { DatabaseMetaData meta = conn.getMetaData(); infoString = " (" + meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion() + " ," + meta.getDriverName() + " " + meta.getDriverVersion() + ")"; } catch (SQLException se) { if (log.isTraceEnabled()) log.trace(se.toString(), se); } } log.info(_loc.get("using-dict", dclass, infoString)); } // set the dictionary's metadata Configurations.configureInstance(dict, conf, props, "DBDictionary"); if (conn != null) { try { dict.connectedConfiguration(conn); } catch (SQLException se) { throw new StoreException(se).setFatal(true); } } return dict; } /** * Guess the dictionary class name to use based on the product string. */ private static String dictionaryClassForString(String prod, JDBCConfiguration conf) { if (StringUtil.isEmpty(prod)) return null; prod = prod.toLowerCase(Locale.ENGLISH); PluginValue dbdictionaryPlugin = ((JDBCConfigurationImpl) conf) .dbdictionaryPlugin; if (prod.indexOf("oracle") != -1) return dbdictionaryPlugin.unalias("oracle"); if (prod.indexOf("sqlserver") != -1) return dbdictionaryPlugin.unalias("sqlserver"); if (prod.indexOf("jsqlconnect") != -1) return dbdictionaryPlugin.unalias("sqlserver"); if (prod.indexOf("mariadb") != -1) return dbdictionaryPlugin.unalias("mariadb"); if (prod.indexOf("mysql") != -1) return dbdictionaryPlugin.unalias("mysql"); if (prod.indexOf("postgres") != -1) return dbdictionaryPlugin.unalias("postgres"); if (prod.indexOf("sybase") != -1) return dbdictionaryPlugin.unalias("sybase"); if (prod.indexOf("adaptive server") != -1) return dbdictionaryPlugin.unalias("sybase"); if (prod.indexOf("informix") != -1 || prod.indexOf("ids") != -1) return dbdictionaryPlugin.unalias("informix"); if (prod.indexOf("ingres") != -1) return dbdictionaryPlugin.unalias("ingres"); if (prod.indexOf("hsql") != -1) return dbdictionaryPlugin.unalias("hsql"); if (prod.indexOf("foxpro") != -1) return dbdictionaryPlugin.unalias("foxpro"); if (prod.indexOf("interbase") != -1) return InterbaseDictionary.class.getName(); if (prod.indexOf("jdatastore") != -1) return JDataStoreDictionary.class.getName(); if (prod.indexOf("borland") != -1) return JDataStoreDictionary.class.getName(); if (prod.indexOf("access") != -1) return dbdictionaryPlugin.unalias("access"); if (prod.indexOf("pointbase") != -1) return dbdictionaryPlugin.unalias("pointbase"); if (prod.indexOf("empress") != -1) return dbdictionaryPlugin.unalias("empress"); if (prod.indexOf("firebird") != -1) return FirebirdDictionary.class.getName(); if (prod.indexOf("cache") != -1) return CacheDictionary.class.getName(); if (prod.indexOf("derby") != -1) return dbdictionaryPlugin.unalias("derby"); if (prod.indexOf("sapdb") != -1) { return dbdictionaryPlugin.unalias("maxdb"); } // test h2 in a special way, because there's a decent chance the string // h2 could appear in the URL of another database if (prod.indexOf("jdbc:h2:") != -1) return dbdictionaryPlugin.unalias("h2"); if (prod.indexOf("h2 database") != -1) return dbdictionaryPlugin.unalias("h2"); // test db2 last, because there's a decent chance this string could // appear in the URL of another database (like if the db is named // "testdb2" or something) if (prod.indexOf("db2") != -1 || prod.indexOf("as400") != -1) return dbdictionaryPlugin.unalias("db2"); if (prod.indexOf("soliddb") != -1) return dbdictionaryPlugin.unalias("soliddb"); // known dbs that we don't support if (prod.indexOf("cloudscape") != -1) return DBDictionary.class.getName(); if (prod.indexOf("daffodil") != -1) return DBDictionary.class.getName(); if (prod.indexOf("idb") != -1) // instantdb return DBDictionary.class.getName(); String prodClassName = dbdictionaryPlugin.unalias(prod); if (!Objects.equals(prod, prodClassName)) return prodClassName; // give up return null; } /** * Return a string containing all the property values of the given * database metadata. */ public static String toString(DatabaseMetaData meta) throws SQLException { String lineSep = J2DoPrivHelper.getLineSeparator(); StringBuilder buf = new StringBuilder(4096); try { buf.append("catalogSeparator: ") .append(meta.getCatalogSeparator()) .append(lineSep) .append("catalogTerm: ") .append(meta.getCatalogTerm()) .append(lineSep) .append("databaseProductName: ") .append(meta.getDatabaseProductName()) .append(lineSep) .append("databaseProductVersion: ") .append(meta.getDatabaseProductVersion()) .append(lineSep) .append("driverName: ") .append(meta.getDriverName()) .append(lineSep) .append("driverVersion: ") .append(meta.getDriverVersion()) .append(lineSep) .append("extraNameCharacters: ") .append(meta.getExtraNameCharacters()) .append(lineSep) .append("identifierQuoteString: ") .append(meta.getIdentifierQuoteString()) .append(lineSep) .append("numericFunctions: ") .append(meta.getNumericFunctions()) .append(lineSep) .append("procedureTerm: ") .append(meta.getProcedureTerm()) .append(lineSep) .append("schemaTerm: ") .append(meta.getSchemaTerm()) .append(lineSep) .append("searchStringEscape: ") .append(meta.getSearchStringEscape()) .append(lineSep) .append("sqlKeywords: ") .append(meta.getSQLKeywords()) .append(lineSep) .append("stringFunctions: ") .append(meta.getStringFunctions()) .append(lineSep) .append("systemFunctions: ") .append(meta.getSystemFunctions()) .append(lineSep) .append("timeDateFunctions: ") .append(meta.getTimeDateFunctions()) .append(lineSep) .append("url: ") .append(meta.getURL()) .append(lineSep) .append("userName: ") .append(meta.getUserName()) .append(lineSep) .append("defaultTransactionIsolation: ") .append(meta.getDefaultTransactionIsolation()) .append(lineSep) .append("driverMajorVersion: ") .append(meta.getDriverMajorVersion()) .append(lineSep) .append("driverMinorVersion: ") .append(meta.getDriverMinorVersion()) .append(lineSep) .append("maxBinaryLiteralLength: ") .append(meta.getMaxBinaryLiteralLength()) .append(lineSep) .append("maxCatalogNameLength: ") .append(meta.getMaxCatalogNameLength()) .append(lineSep) .append("maxCharLiteralLength: ") .append(meta.getMaxCharLiteralLength()) .append(lineSep) .append("maxColumnNameLength: ") .append(meta.getMaxColumnNameLength()) .append(lineSep) .append("maxColumnsInGroupBy: ") .append(meta.getMaxColumnsInGroupBy()) .append(lineSep) .append("maxColumnsInIndex: ") .append(meta.getMaxColumnsInIndex()) .append(lineSep) .append("maxColumnsInOrderBy: ") .append(meta.getMaxColumnsInOrderBy()) .append(lineSep) .append("maxColumnsInSelect: ") .append(meta.getMaxColumnsInSelect()) .append(lineSep) .append("maxColumnsInTable: ") .append(meta.getMaxColumnsInTable()) .append(lineSep) .append("maxConnections: ") .append(meta.getMaxConnections()) .append(lineSep) .append("maxCursorNameLength: ") .append(meta.getMaxCursorNameLength()) .append(lineSep) .append("maxIndexLength: ") .append(meta.getMaxIndexLength()) .append(lineSep) .append("maxProcedureNameLength: ") .append(meta.getMaxProcedureNameLength()) .append(lineSep) .append("maxRowSize: ") .append(meta.getMaxRowSize()) .append(lineSep) .append("maxSchemaNameLength: ") .append(meta.getMaxSchemaNameLength()) .append(lineSep) .append("maxStatementLength: ") .append(meta.getMaxStatementLength()) .append(lineSep) .append("maxStatements: ") .append(meta.getMaxStatements()) .append(lineSep) .append("maxTableNameLength: ") .append(meta.getMaxTableNameLength()) .append(lineSep) .append("maxTablesInSelect: ") .append(meta.getMaxTablesInSelect()) .append(lineSep) .append("maxUserNameLength: ") .append(meta.getMaxUserNameLength()) .append(lineSep) .append("isCatalogAtStart: ") .append(meta.isCatalogAtStart()) .append(lineSep) .append("isReadOnly: ") .append(meta.isReadOnly()) .append(lineSep) .append("nullPlusNonNullIsNull: ") .append(meta.nullPlusNonNullIsNull()) .append(lineSep) .append("nullsAreSortedAtEnd: ") .append(meta.nullsAreSortedAtEnd()) .append(lineSep) .append("nullsAreSortedAtStart: ") .append(meta.nullsAreSortedAtStart()) .append(lineSep) .append("nullsAreSortedHigh: ") .append(meta.nullsAreSortedHigh()) .append(lineSep) .append("nullsAreSortedLow: ") .append(meta.nullsAreSortedLow()) .append(lineSep) .append("storesLowerCaseIdentifiers: ") .append(meta.storesLowerCaseIdentifiers()) .append(lineSep) .append("storesLowerCaseQuotedIdentifiers: ") .append(meta.storesLowerCaseQuotedIdentifiers()) .append(lineSep) .append("storesMixedCaseIdentifiers: ") .append(meta.storesMixedCaseIdentifiers()) .append(lineSep) .append("storesMixedCaseQuotedIdentifiers: ") .append(meta.storesMixedCaseQuotedIdentifiers()) .append(lineSep) .append("storesUpperCaseIdentifiers: ") .append(meta.storesUpperCaseIdentifiers()) .append(lineSep) .append("storesUpperCaseQuotedIdentifiers: ") .append(meta.storesUpperCaseQuotedIdentifiers()) .append(lineSep) .append("supportsAlterTableWithAddColumn: ") .append(meta.supportsAlterTableWithAddColumn()) .append(lineSep) .append("supportsAlterTableWithDropColumn: ") .append(meta.supportsAlterTableWithDropColumn()) .append(lineSep) .append("supportsANSI92EntryLevelSQL: ") .append(meta.supportsANSI92EntryLevelSQL()) .append(lineSep) .append("supportsANSI92FullSQL: ") .append(meta.supportsANSI92FullSQL()) .append(lineSep) .append("supportsANSI92IntermediateSQL: ") .append(meta.supportsANSI92IntermediateSQL()) .append(lineSep) .append("supportsCatalogsInDataManipulation: ") .append(meta.supportsCatalogsInDataManipulation()) .append(lineSep) .append("supportsCatalogsInIndexDefinitions: ") .append(meta.supportsCatalogsInIndexDefinitions()) .append(lineSep) .append("supportsCatalogsInPrivilegeDefinitions: ") .append(meta.supportsCatalogsInPrivilegeDefinitions()) .append(lineSep) .append("supportsCatalogsInProcedureCalls: ") .append(meta.supportsCatalogsInProcedureCalls()) .append(lineSep) .append("supportsCatalogsInTableDefinitions: ") .append(meta.supportsCatalogsInTableDefinitions()) .append(lineSep) .append("supportsColumnAliasing: ") .append(meta.supportsColumnAliasing()) .append(lineSep) .append("supportsConvert: ") .append(meta.supportsConvert()) .append(lineSep) .append("supportsCoreSQLGrammar: ") .append(meta.supportsCoreSQLGrammar()) .append(lineSep) .append("supportsCorrelatedSubqueries: ") .append(meta.supportsCorrelatedSubqueries()) .append(lineSep) .append( "supportsDataDefinitionAndDataManipulationTransactions: ") .append(meta. supportsDataDefinitionAndDataManipulationTransactions()) .append(lineSep) .append("supportsDataManipulationTransactionsOnly: ") .append(meta.supportsDataManipulationTransactionsOnly()) .append(lineSep) .append("supportsDifferentTableCorrelationNames: ") .append(meta.supportsDifferentTableCorrelationNames()) .append(lineSep) .append("supportsExpressionsInOrderBy: ") .append(meta.supportsExpressionsInOrderBy()) .append(lineSep) .append("supportsExtendedSQLGrammar: ") .append(meta.supportsExtendedSQLGrammar()) .append(lineSep) .append("supportsFullOuterJoins: ") .append(meta.supportsFullOuterJoins()) .append(lineSep) .append("supportsGroupBy: ") .append(meta.supportsGroupBy()) .append(lineSep) .append("supportsGroupByBeyondSelect: ") .append(meta.supportsGroupByBeyondSelect()) .append(lineSep) .append("supportsGroupByUnrelated: ") .append(meta.supportsGroupByUnrelated()) .append(lineSep) .append("supportsIntegrityEnhancementFacility: ") .append(meta.supportsIntegrityEnhancementFacility()) .append(lineSep) .append("supportsLikeEscapeClause: ") .append(meta.supportsLikeEscapeClause()) .append(lineSep) .append("supportsLimitedOuterJoins: ") .append(meta.supportsLimitedOuterJoins()) .append(lineSep) .append("supportsMinimumSQLGrammar: ") .append(meta.supportsMinimumSQLGrammar()) .append(lineSep) .append("supportsMixedCaseIdentifiers: ") .append(meta.supportsMixedCaseIdentifiers()) .append(lineSep) .append("supportsMixedCaseQuotedIdentifiers: ") .append(meta.supportsMixedCaseQuotedIdentifiers()) .append(lineSep) .append("supportsMultipleResultSets: ") .append(meta.supportsMultipleResultSets()) .append(lineSep) .append("supportsMultipleTransactions: ") .append(meta.supportsMultipleTransactions()) .append(lineSep) .append("supportsNonNullableColumns: ") .append(meta.supportsNonNullableColumns()) .append(lineSep) .append("supportsOpenCursorsAcrossCommit: ") .append(meta.supportsOpenCursorsAcrossCommit()) .append(lineSep) .append("supportsOpenCursorsAcrossRollback: ") .append(meta.supportsOpenCursorsAcrossRollback()) .append(lineSep) .append("supportsOpenStatementsAcrossCommit: ") .append(meta.supportsOpenStatementsAcrossCommit()) .append(lineSep) .append("supportsOpenStatementsAcrossRollback: ") .append(meta.supportsOpenStatementsAcrossRollback()) .append(lineSep) .append("supportsOrderByUnrelated: ") .append(meta.supportsOrderByUnrelated()) .append(lineSep) .append("supportsOuterJoins: ") .append(meta.supportsOuterJoins()) .append(lineSep) .append("supportsPositionedDelete: ") .append(meta.supportsPositionedDelete()) .append(lineSep) .append("supportsPositionedUpdate: ") .append(meta.supportsPositionedUpdate()) .append(lineSep) .append("supportsSchemasInDataManipulation: ") .append(meta.supportsSchemasInDataManipulation()) .append(lineSep) .append("supportsSchemasInIndexDefinitions: ") .append(meta.supportsSchemasInIndexDefinitions()) .append(lineSep) .append("supportsSchemasInPrivilegeDefinitions: ") .append(meta.supportsSchemasInPrivilegeDefinitions()) .append(lineSep) .append("supportsSchemasInProcedureCalls: ") .append(meta.supportsSchemasInProcedureCalls()) .append(lineSep) .append("supportsSchemasInTableDefinitions: ") .append(meta.supportsSchemasInTableDefinitions()) .append(lineSep) .append("supportsSelectForUpdate: ") .append(meta.supportsSelectForUpdate()) .append(lineSep) .append("supportsStoredProcedures: ") .append(meta.supportsStoredProcedures()) .append(lineSep) .append("supportsSubqueriesInComparisons: ") .append(meta.supportsSubqueriesInComparisons()) .append(lineSep) .append("supportsSubqueriesInExists: ") .append(meta.supportsSubqueriesInExists()) .append(lineSep) .append("supportsSubqueriesInIns: ") .append(meta.supportsSubqueriesInIns()) .append(lineSep) .append("supportsSubqueriesInQuantifieds: ") .append(meta.supportsSubqueriesInQuantifieds()) .append(lineSep) .append("supportsTableCorrelationNames: ") .append(meta.supportsTableCorrelationNames()) .append(lineSep) .append("supportsTransactions: ") .append(meta.supportsTransactions()) .append(lineSep) .append("supportsUnion: ") .append(meta.supportsUnion()) .append(lineSep) .append("supportsUnionAll: ") .append(meta.supportsUnionAll()) .append(lineSep) .append("usesLocalFilePerTable: ") .append(meta.usesLocalFilePerTable()) .append(lineSep) .append("usesLocalFiles: ") .append(meta.usesLocalFiles()) .append(lineSep) .append("allProceduresAreCallable: ") .append(meta.allProceduresAreCallable()) .append(lineSep) .append("allTablesAreSelectable: ") .append(meta.allTablesAreSelectable()) .append(lineSep) .append("dataDefinitionCausesTransactionCommit: ") .append(meta.dataDefinitionCausesTransactionCommit()) .append(lineSep) .append("dataDefinitionIgnoredInTransactions: ") .append(meta.dataDefinitionIgnoredInTransactions()) .append(lineSep) .append("doesMaxRowSizeIncludeBlobs: ") .append(meta.doesMaxRowSizeIncludeBlobs()) .append(lineSep) .append("supportsBatchUpdates: ") .append(meta.supportsBatchUpdates()); } catch (Throwable t) { // maybe abstract method error for jdbc 3 metadata method, or // other error buf.append(lineSep).append("Caught throwable: ").append(t); } return buf.toString(); } }