/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://oss.oracle.com/licenses/CDDL+GPL-1.1
* or LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.persistence.common.database;
import java.io.IOException;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.glassfish.persistence.common.I18NHelper;
import com.sun.logging.LogDomains;
/**
* @author Mitesh Meswani
* This class defines string constants representing database names.
* This class is responsible to translate given database name to internal name.
*/
public class DBVendorTypeHelper {
//Enum that corresponds to unknown database.
public final static int OTHER_ENUM = -1;
//Known databases.
public final static int DEFAULT_DB_ENUM = 0;
public final static int ORACLE_ENUM = 1;
public final static int POINTBASE_ENUM = 2;
public final static int MSSQL_ENUM = 3;
public final static int SYBASE_ENUM = 4;
public final static int DB2_ENUM = 5;
public final static int MYSQL_ENUM = 6;
public final static int INFORMIX_ENUM = 7;
public final static int INGRES_ENUM = 8;
public final static int DERBY_ENUM = 9;
public final static int SYMFOWARE_ENUM = 10;
//Please increment following when a new known database is added.
public final static int MAX_KNOWN_DB = 11;
/**
* Array that defines mapping from given string name to enum.
* Please make sure that array indexes and enum values are kept in sync.
*/
private final static String enumToStringMapping[] =
{"SQL92", "ORACLE", "POINTBASE", "MSSQL", "SYBASE", "DB2", "MYSQL",
"INFORMIX", "INGRES", "DERBY", "SYMFOWARE"}; // NOI18N
public final static String DEFAULT_DB = enumToStringMapping[DEFAULT_DB_ENUM];
public final static String ORACLE = enumToStringMapping[ORACLE_ENUM];
public final static String POINTBASE = enumToStringMapping[POINTBASE_ENUM];
public final static String MSSQL = enumToStringMapping[MSSQL_ENUM];
public final static String SYBASE = enumToStringMapping[SYBASE_ENUM];
public final static String DB2 = enumToStringMapping[DB2_ENUM];
public final static String MYSQL = enumToStringMapping[MYSQL_ENUM];
public final static String INFORMIX = enumToStringMapping[INFORMIX_ENUM];
public final static String INGRES = enumToStringMapping[INGRES_ENUM];
public final static String DERBY = enumToStringMapping[DERBY_ENUM];
public final static String SYMFOWARE = enumToStringMapping[SYMFOWARE_ENUM];
private final static String PROPERTY_PATH = "org/glassfish/persistence/common/"; // NOI18N
private final static String VENDOR_NAME_TO_TYPE_RESOURCE_DEFAULT_NAME =
PROPERTY_PATH + "VendorNameToTypeMapping.properties"; //NOI18N
// Preserve old names even though this class is now in a different package
private final static String VENDOR_NAME_TO_TYPE_PROPERTY =
"com.sun.jdo.spi.persistence.utility.database.VENDOR_NAME_TO_TYPE"; //NOI18N
private final static String VENDOR_NAME_TO_TYPE_RESOURCE_PROPERTY =
"com.sun.jdo.spi.persistence.utility.database.VENDOR_NAME_TO_TYPE_RESOURCE"; //NOI18N
/** The logger */
private final static Logger logger = LogDomains.getLogger(
DBVendorTypeHelper.class, LogDomains.JDO_LOGGER);
/** I18N message handler */
private final static ResourceBundle messages = I18NHelper.loadBundle(
"org.glassfish.persistence.common.LogStrings", //NOI18N
DBVendorTypeHelper.class.getClassLoader());
/**
* Holds mapping between possible vendor names to internal types defined above.
* vendor names are treated as regular expressions.
*/
private static Properties _nameToVendorType = initializeNameToVendorType();
/** Get Database Vendor Type from vendor name.
* @param vendorName Input vendor name. Typically this is obtained by querying
* <code>DatabaseMetaData</code>.
* @return Database type that corresponds to <code>vendorName</code>.
* If vendorName does not match any of predefined vendor names, the database type
* returned is generated using following logic.
* <PRE>
* detectedDbType = vendorName.toUpperCase(Locale.ENGLISH);
* int i = detectedDbType.indexOf('/');
* if (i > -1) {
* detectedDbType = detectedDbType.substring(0, i);
* }
* </PRE>
* If vendorName is null, <code>DEFAULT_DB</code> is returned.
*/
public static String getDBType(String vendorName) {
boolean debug = logger.isLoggable(Level.FINE);
if(debug) {
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.inputVendorName", //NOI18N
vendorName));
}
String detectedDbType = DEFAULT_DB;
if(vendorName != null) {
detectedDbType = matchVendorNameInProperties(vendorName, _nameToVendorType);
//If not able to detect dbType from properties, invent one by
//manipulating input vendorName.
if(detectedDbType == null) {
detectedDbType = vendorName.toUpperCase(Locale.ENGLISH);
int i = detectedDbType.indexOf('/');
if (i > -1) {
detectedDbType = detectedDbType.substring(0, i);
}
}
}
if(debug)
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.detectedVendorType",detectedDbType)); //NOI18N
return detectedDbType;
}
/** Gets enumerated database type for given metaData
* @param metaData Input DataBaseMetaData.
* @return Enumerated database type as described by
* {@link #getEnumDBType(java.lang.String)}.
*/
public static int getEnumDBType(DatabaseMetaData metaData)
throws SQLException {
String dbType = getDBType(metaData.getDatabaseProductName());
return getEnumDBType(dbType);
}
/** Gets enumerated database type for given dbType
* @param dbType Input database Type. This should have been obtained from a previous call to
* {@link #getDBType(java.lang.String)}
* @return dbType translated to one of the enumerations above. If dbType does not correspond to
* one of the predefined types, OTHER is returned
*/
public static int getEnumDBType(String dbType) {
int enumDBType = OTHER_ENUM;
//Search through the array for dbType
for(int i = 0; i < MAX_KNOWN_DB - 1 && enumDBType == OTHER_ENUM; i++) {
if(enumToStringMapping[i].equals(dbType) )
enumDBType = i;
}
return enumDBType;
}
/**
* Determines whether to use uppercase schema name for a give database.
* @param dmd The DatabaseMetaData for the database
* @return true if upper case schemaname is to be used. False otherwise.
* @throws SQLException
*/
public static boolean requireUpperCaseSchema(DatabaseMetaData dmd)
throws SQLException {
int vendorTypeEnum = getEnumDBType(dmd);
return ORACLE_ENUM == vendorTypeEnum ||
POINTBASE_ENUM == vendorTypeEnum;
}
/**
* Allocate and initialize nameToVendorType if not already done.
*/
private static Properties initializeNameToVendorType() {
synchronized(DBVendorTypeHelper.class) {
if(_nameToVendorType == null) {
_nameToVendorType = new Properties();
String resourceName = System.getProperty(VENDOR_NAME_TO_TYPE_RESOURCE_PROPERTY,
VENDOR_NAME_TO_TYPE_RESOURCE_DEFAULT_NAME);
try {
PropertyHelper.loadFromResource(_nameToVendorType,resourceName,
DBVendorTypeHelper.class.getClassLoader() );
} catch (IOException e) {
if(logger.isLoggable(Level.FINE) ) {
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.couldNotLoadResource", // NOI18N
resourceName),e);
}
}
overrideWithSystemProperties(_nameToVendorType);
}
}
return _nameToVendorType;
}
/**
* Match vendorName in properties specifieid by nameToVendorType.
*/
private static String matchVendorNameInProperties(String vendorName, Properties nameToVendorType) {
String dbType = null;
//Iterate over all properties till we find match.
for( Iterator iterator = nameToVendorType.entrySet().iterator();
dbType == null && iterator.hasNext();) {
Map.Entry entry = (Map.Entry) iterator.next();
String regExpr = (String) entry.getKey();
String value = (String) entry.getValue();
if(logger.isLoggable(Level.FINEST) )
logger.finest(I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.regExprDbType",regExpr,value)); // NOI18N
if( matchPattern(regExpr,vendorName) ) {
dbType = value;
}
}
return dbType;
}
/** Matches target to pattern specified regExp. Returns false if there is
* any error compiling regExp.
* @param regExp The regular expression.
* @param target The target against which we are trying to match regExp.
* @return false if there is error compiling regExp or target does not
* match regExp. true if regExp matches pattern.
*/
private static boolean matchPattern(String regExp, String target) {
boolean matches = false;
try {
matches = Pattern.matches(regExp,target);
} catch (PatternSyntaxException e){
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.patternSyntaxException"),e); // NOI18N
}
return matches;
}
/**
* Overrides nameToVendorType with any system properties defined.
*/
private static void overrideWithSystemProperties(Properties nameToVendorType) {
String vendorNameToType = null;
boolean debug = logger.isLoggable(Level.FINE);
int counter = 1;
do {
String vendorNameToTypeProperty = VENDOR_NAME_TO_TYPE_PROPERTY + counter++;
vendorNameToType = System.getProperty(vendorNameToTypeProperty);
if(vendorNameToType != null) {
//Split the vendorNameToType into two at char '='
String[] parsedProperty = vendorNameToType.split("=",2); //NOI18N
if( parsedProperty.length >= 2) {
String suggestedDbType = parsedProperty[0];
String regExp = parsedProperty[1];
if(debug) {
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.traceVendorNameToTypeProperty", //NOI18N
vendorNameToTypeProperty,regExp,suggestedDbType));
}
nameToVendorType.put(regExp,suggestedDbType);
}
else {
if(debug)
logger.log(Level.FINE, I18NHelper.getMessage(messages,
"database.DBVendorTypeHelper.errorParsingVendorNameToTypeProperty", //NOI18N
vendorNameToTypeProperty,vendorNameToType));
}
}
} while (vendorNameToType != null);
}
}