All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sun.enterprise.connectors.util.DriverLoader Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2009-2011 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://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/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 packager/legal/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.
 */
// Portions Copyright 2018-2022 Payara Foundation and/or affiliates

package com.sun.enterprise.connectors.util;

import com.sun.appserv.connectors.internal.api.ConnectorConstants;
import com.sun.enterprise.connectors.ConnectorRuntime;
import com.sun.enterprise.util.SystemPropertyConstants;
import com.sun.logging.LogDomains;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import org.jvnet.hk2.annotations.Service;

/**
 * Driver Loader to load the jdbc drivers and get driver/datasource classnames
 * by introspection.
 *
 * @author Shalini M
 */
@Service
public class DriverLoader implements ConnectorConstants {

    private static Logger logger =
    LogDomains.getLogger(DriverLoader.class, LogDomains.RSR_LOGGER);

    private static final String DRIVER_INTERFACE_NAME="java.sql.Driver";
    private static final String SERVICES_DRIVER_IMPL_NAME = "META-INF/services/java.sql.Driver";
    private static final String DATABASE_VENDOR_H2 = "H2";
    private static final String DATABASE_VENDOR_DERBY = "DERBY";
    private static final String DATABASE_VENDOR_JAVADB = "JAVADB";
    private static final String DATABASE_VENDOR_EMBEDDED_DERBY = "EMBEDDED-DERBY";
    private static final String DATABASE_VENDOR_DERBY_30 = "DERBY-30";
    private static final String DATABASE_VENDOR_EMBEDDED_DERBY_30 = "EMBEDDED-DERBY-30";
    private static final String DATABASE_VENDOR_JAVADB_30 = "JAVADB-30";
    private static final String DATABASE_VENDOR_MSSQLSERVER = "MICROSOFTSQLSERVER";
    private static final String DATABASE_VENDOR_SUN_SQLSERVER = "SUN-SQLSERVER";
    private static final String DATABASE_VENDOR_SUN_ORACLE = "SUN-ORACLE";
    private static final String DATABASE_VENDOR_SUN_DB2 = "SUN-DB2";
    private static final String DATABASE_VENDOR_SUN_SYBASE = "SUN-SYBASE";
    private static final String DATABASE_VENDOR_SYBASE = "SYBASE";
    private static final String DATABASE_VENDOR_ORACLE = "ORACLE";
    private static final String DATABASE_VENDOR_DB2 = "DB2";
    private static final String DATABASE_VENDOR_EMBEDDED = "EMBEDDED";
    private static final String DATABASE_VENDOR_30 = "30";
    private static final String DATABASE_VENDOR_40 = "40";
    
    private static final String DATABASE_VENDOR_SQLSERVER = "SQLSERVER";
    private static final String DBVENDOR_MAPPINGS_ROOT = 
            System.getProperty(ConnectorConstants.INSTALL_ROOT) + File.separator + 
            "lib" + File.separator + "install" + File.separator + "databases" +
            File.separator + "dbvendormapping" + File.separator;
    private final static String DS_PROPERTIES = "ds.properties";
    private final static String CPDS_PROPERTIES = "cpds.properties";
    private final static String XADS_PROPERTIES = "xads.properties";
    private final static String DRIVER_PROPERTIES = "driver.properties";
    private final String VENDOR_PROPERTIES = "dbvendor.properties";

    /**
     * Get a set of common database vendor names supported in glassfish.
     * @return database vendor names set.
     */
    public Set getDatabaseVendorNames() {
        File dbVendorFile = new File(DBVENDOR_MAPPINGS_ROOT + VENDOR_PROPERTIES);
        Properties fileProperties = loadFile(dbVendorFile);
        Set dbvendorNames = new TreeSet();

        Enumeration e = fileProperties.propertyNames();
        while(e.hasMoreElements()) {
            String vendor = (String) e.nextElement();
            dbvendorNames.add(vendor);
        }
        return dbvendorNames;
    }

    public static File getResourceTypeFile(String resType) {
        File mappingFile = null;
        if(ConnectorConstants.JAVAX_SQL_DATASOURCE.equals(resType)) {
            mappingFile = new File(DBVENDOR_MAPPINGS_ROOT + DS_PROPERTIES);
        } else if(ConnectorConstants.JAVAX_SQL_XA_DATASOURCE.equals(resType)) {
            mappingFile = new File(DBVENDOR_MAPPINGS_ROOT + XADS_PROPERTIES);
        } else if(ConnectorConstants.JAVAX_SQL_CONNECTION_POOL_DATASOURCE.equals(resType)) {
            mappingFile = new File(DBVENDOR_MAPPINGS_ROOT + CPDS_PROPERTIES);
        } else if(ConnectorConstants.JAVA_SQL_DRIVER.equals(resType)) {
            mappingFile = new File(DBVENDOR_MAPPINGS_ROOT + DRIVER_PROPERTIES);
        }
        return mappingFile;
    }

    public static Properties loadFile(File mappingFile) {
        Properties fileProperties = new Properties();
        if (mappingFile != null && mappingFile.exists()) {
            try (FileInputStream fis = new FileInputStream(mappingFile)) {
                fileProperties.load(fis);
            } catch (IOException ioe) {
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("IO Exception during properties load : "
                            + mappingFile.getAbsolutePath());
                }
            }
        } else {
            if (logger.isLoggable(Level.FINE)) {
                logger.fine("File not found : " + (mappingFile == null ? "null" : mappingFile.getAbsolutePath()));
            }
        }
        return fileProperties;
    }


    private static String getImplClassNameFromMapping(String dbVendor, String resType) {
        File mappingFile = getResourceTypeFile(resType);
        Properties fileProperties = loadFile(mappingFile);
        return fileProperties.getProperty(dbVendor.toUpperCase(Locale.getDefault()));
    }
    
    /**
     * Get equivalent name for the database vendor name. This is useful for
     * introspection as the vendor name for oracle and sun oracle type of jdbc
     * drivers are the same. 
     * @param dbVendor
     * @return
     */
    private String getEquivalentName(String dbVendor) {
        if (dbVendor.toUpperCase(Locale.getDefault()).startsWith(DATABASE_VENDOR_JAVADB) ||
                dbVendor.equalsIgnoreCase(DATABASE_VENDOR_EMBEDDED_DERBY) ||
                dbVendor.equalsIgnoreCase(DATABASE_VENDOR_EMBEDDED_DERBY_30) ||
                dbVendor.equalsIgnoreCase(DATABASE_VENDOR_DERBY_30) ||
                dbVendor.equalsIgnoreCase(DATABASE_VENDOR_JAVADB_30)) {
            return DATABASE_VENDOR_DERBY;
        } else if (dbVendor.equalsIgnoreCase(DATABASE_VENDOR_MSSQLSERVER) ||
                dbVendor.equalsIgnoreCase(DATABASE_VENDOR_SUN_SQLSERVER)) {
            return DATABASE_VENDOR_SQLSERVER;
        } else if (dbVendor.equalsIgnoreCase(DATABASE_VENDOR_SUN_DB2)) {
            return DATABASE_VENDOR_DB2;
        } else if (dbVendor.equalsIgnoreCase(DATABASE_VENDOR_SUN_ORACLE)) {
            return DATABASE_VENDOR_ORACLE;
        } else if (dbVendor.equalsIgnoreCase(DATABASE_VENDOR_SUN_SYBASE)) {
            return DATABASE_VENDOR_SYBASE;
        }
        return null;    
    }

    public Set getJdbcDriverClassNames(String dbVendor, String resType) {
        //Do not use introspection by default.
        return getJdbcDriverClassNames(dbVendor, resType, false);
    }
    
    /**
     * Gets a set of driver or datasource classnames for the particular vendor.
     * Loads the jdbc driver, introspects the jdbc driver jar and gets the 
     * classnames.
     * Based on whether introspect flag is turned on or off, the classnames are
     * retrieved by introspection or from a pre-defined list.
     * @return
     */
    public Set getJdbcDriverClassNames(String dbVendor, String resType,
            boolean introspect) {
        //Map of all jar files with the set of driver implementations. every file
        // that is a jdbc jar will have a set of driver impls.
        Set implClassNames = new TreeSet();
        Set allImplClassNames = new TreeSet();
        //Used for introspection.
        String vendor = null;

        if(dbVendor != null) {
            dbVendor = dbVendor.trim().replaceAll(" ", "");
            vendor = getEquivalentName(dbVendor);
            if (vendor == null) {
                vendor = dbVendor;
            }
        }

        if(!introspect) {
            //Get from the pre-populated list. This is done for common dbvendor names
            String implClass = getImplClassNameFromMapping(dbVendor, resType);
        
            if(implClass != null) {
                allImplClassNames.add(implClass);
                return allImplClassNames;
            }
        }
        
        List jarFileLocations = getJdbcDriverLocations();
        Set allJars = new HashSet();
        
        if(jarFileLocations != null) {
            for(File lib : jarFileLocations) {
                if(lib.isDirectory()) {
                    for(File file : lib.listFiles(new JarFileFilter())) {
                        allJars.add(file);
                    }
                }            
            }
        }
        for (File file : allJars) {
            if (file.isFile()) {
                //Introspect jar and get classnames.
                if(vendor != null) {
                    implClassNames = introspectAndLoadJar(file, resType, vendor, dbVendor);
                }
                //Found the impl classnames for the particular dbVendor. 
                //Hence no need to search in other jar files.
                if(!implClassNames.isEmpty()) {
                    for(String className : implClassNames) {
                        allImplClassNames.add(className);
                    }
                }
            }
        }        

        return allImplClassNames;
    }

    private Set getImplClassesByIteration(File f, String resType, 
            String dbVendor, String origDbVendor) {
        SortedSet implClassNames = new TreeSet();
        String implClass = null;
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(f);
            Enumeration e = jarFile.entries();
            while(e.hasMoreElements()) {

                ZipEntry zipEntry = (ZipEntry) e.nextElement();

                if (zipEntry != null) {

                    String entry = zipEntry.getName();
                    if (DRIVER_INTERFACE_NAME.equals(resType)) {
                        if (SERVICES_DRIVER_IMPL_NAME.equals(entry)) {

                            InputStream metaInf = jarFile.getInputStream(zipEntry);
                            implClass = processMetaInf(metaInf);
                            if (implClass != null) {
                                if (isLoaded(implClass, resType)) {
                                    //Add to the implClassNames only if vendor name matches.
                                    if(isVendorSpecific(f, dbVendor, implClass, origDbVendor)) {
                                        implClassNames.add(implClass);
                                    }
                                }
                            }
                            if(logger.isLoggable(Level.FINEST)) {
                                logger.finest("Driver loader : implClass = " + implClass);
                            }
                            
                        }
                    }
                    if (entry.endsWith(".class")) {
                        //Read from metainf file for all jdbc40 drivers and resType
                        //java.sql.Driver.TODO : this should go outside .class check.
                        //TODO : Some classnames might not have these strings
                        //in their classname. Logic should be flexible in such cases.
                        if (entry.toUpperCase(Locale.getDefault()).indexOf("DATASOURCE") != -1 ||
                                entry.toUpperCase(Locale.getDefault()).indexOf("DRIVER") != -1) {
                            implClass = getClassName(entry);
                            if (implClass != null) {
                                if (isLoaded(implClass, resType)) {
                                    if (isVendorSpecific(f, dbVendor, implClass, origDbVendor)) {
                                        implClassNames.add(implClass);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Error while getting Jdbc driver classnames ", ex);
        } finally {
            if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException ex) {
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "Exception while closing JarFile '"
                                + jarFile.getName() + "' :", ex);
                    }
                }
            }
        }
        //Could be one or many depending on the connection definition class name 
        return implClassNames;
    }

    /**
     * Returns a list of all driver class names that were loaded from the jar file.
     * @param f
     * @param dbVendor
     * @return Set of driver/datasource class implementations based on resType
     */
    private Set introspectAndLoadJar(File f, String resType, 
            String dbVendor, String origDbVendor) {

        if(logger.isLoggable(Level.FINEST)) {
            logger.finest("DriverLoader : introspectAndLoadJar ");
        }
       
        return getImplClassesByIteration(f, resType, dbVendor, origDbVendor);
                
    }

    private boolean isNotAbstract(Class cls) {
        int modifier = cls.getModifiers();
        return !Modifier.isAbstract(modifier);
    }

    /**
     * Reads the META-INF/services/java.sql.Driver file contents and returns
     * the driver implementation class name.
     * In case of jdbc40 drivers, the META-INF/services/java.sql.Driver file
     * contains the name of the driver class.
     * @param metaInf
     * @return driver implementation class name
     */
    private String processMetaInf(InputStream metaInf) {
        String driverClassName = null;
        InputStreamReader reader = null;
        BufferedReader buffReader = null;
        try {
            reader = new InputStreamReader(metaInf);
            buffReader = new BufferedReader(reader);
            String line;
            while ((line = buffReader.readLine()) != null) {
                driverClassName = line;
            }
        } catch(IOException ioex) {
            if (logger.isLoggable(Level.FINEST)) {
                logger.finest("DriverLoader : exception while processing "
                        + "META-INF directory for DriverClassName " + ioex);
            }
        } finally {
            try {
                if(buffReader != null)
                    buffReader.close();
            } catch (IOException ex) {
                if(logger.isLoggable(Level.FINE)) {
                    logger.log(Level.FINE, "Error while closing File handles after reading META-INF files : ", ex);
                }
            }
            try {
                if(reader != null)
                    reader.close();
            } catch (IOException ex) {
                if(logger.isLoggable(Level.FINE)) {
                    logger.log(Level.FINE, "Error while closing File handles after reading META-INF files : ", ex);
                }
            }
        }
        return driverClassName;
    }
    
    /**
     * Check if the classname has been loaded and if it is a Driver or a 
     * DataSource impl.
     * @param classname
     * @return
     */
    private boolean isLoaded(String classname, String resType) {
        Class cls = null;
        try {
            //This will fail in case the driver is not in classpath.
            cls = ConnectorRuntime.getRuntime().getConnectorClassLoader().loadClass(classname);
        //Check shud be made here to look into the lib directory now to see
        // if there are any newly installed drivers.
        //If so, create a URLClassLoader and load the class with common
        //classloader as the parent.
        } catch (Exception ex) {
            cls = null;
        } catch (Throwable t) {
            cls = null;
        } 
        return (isResType(cls, resType));
    }
    
    /**
     * Find if the particular class has any implementations of java.sql.Driver or
     * javax.sql.DataSource or any other resTypes passed.
     * @param cls
     * @return
     */
    private boolean isResType(Class cls, String resType) {
        boolean isResType = false;
        if (cls != null) {
            if("javax.sql.DataSource".equals(resType)) {
                if(javax.sql.DataSource.class.isAssignableFrom(cls)) {
                    isResType = isNotAbstract(cls);
                }
            } else if("javax.sql.ConnectionPoolDataSource".equals(resType)) {
                if(javax.sql.ConnectionPoolDataSource.class.isAssignableFrom(cls)) {
                    isResType = isNotAbstract(cls);
                }
            } else if("javax.sql.XADataSource".equals(resType)) {
                if(javax.sql.XADataSource.class.isAssignableFrom(cls)) {
                    isResType = isNotAbstract(cls);
                }
            } else if("java.sql.Driver".equals(resType)) {
                if(java.sql.Driver.class.isAssignableFrom(cls)) {
                    isResType = isNotAbstract(cls);
                }
            }
        }
        return isResType;
    }

    
    /**
     * This method should be executed before driverLoaders are queries for 
     * classloader to load the classname.
     * @param classname
     * @return
     */
    //TODO remove later
    /*private boolean loadClass(File f, String classname, String resType) {
        List urls = new ArrayList();
        Class urlCls = null;
        boolean isLoaded = false;
        try {
            urls.add(f.toURI().toURL());
        } catch (MalformedURLException ex) {
        }
        ClassLoader loader = ConnectorRuntime.getRuntime().getConnectorClassLoader();
        if (!urls.isEmpty()) {
            ClassLoader urlClassLoader =
                    new URLClassLoader(urls.toArray(new URL[urls.size()]), loader);
            try {
                urlCls = urlClassLoader.loadClass(classname);
            } catch (ClassNotFoundException ex) {
            }
            isLoaded = isResType(urlCls, resType);
            if(isLoaded) {
                //Loaded the class and verified it to be a jdbc driver implementing
                //java.sql.Driver or implementing javax.sql.DataSource
                //Register the url classloader for later use.
                //For ojdbc14 n ojdbc5 (same class names different url class loaders
                //will be added.
                classLoaders.put(classname, urlClassLoader);
            }
        } else {
        }
        return isLoaded;
    }*/

    private String getClassName(String classname) {
        classname = classname.replaceAll("/", ".");
        classname = classname.substring(0, classname.lastIndexOf(".class"));
        return classname;
    }

    private boolean isVendorSpecific(File f, String dbVendor, String className,
            String origDbVendor) {
        //File could be a jdbc jar file or a normal jar file
        boolean isVendorSpecific = false;

        if(origDbVendor != null) {
            if(origDbVendor.equalsIgnoreCase(DATABASE_VENDOR_H2)) {
                return className.toUpperCase(Locale.getDefault()).indexOf(DATABASE_VENDOR_H2) != -1;
                    }
                }

        String vendor = getVendorFromManifest(f);

        if (vendor == null) {
            //might have to do this part by going through the class names or some other method.
            //dbVendor might be used in this portion
            if (isVendorSpecific(dbVendor, className)) {
                isVendorSpecific = true;
            }
        } else {
            //Got from Manifest file.
            if (vendor.equalsIgnoreCase(dbVendor) || 
                    vendor.toUpperCase(Locale.getDefault()).indexOf(
                    dbVendor.toUpperCase(Locale.getDefault())) != -1) {
                isVendorSpecific = true;
            }
        }
        if(isVendorSpecific) {
            if(origDbVendor != null && origDbVendor.endsWith(DATABASE_VENDOR_30)) {
                if(origDbVendor.equalsIgnoreCase(DATABASE_VENDOR_EMBEDDED_DERBY_30)) {
                    return className.toUpperCase(Locale.getDefault()).indexOf(DATABASE_VENDOR_EMBEDDED) != -1;
                }
                return !(className.toUpperCase(Locale.getDefault()).endsWith(DATABASE_VENDOR_40));
            }
        }
        return isVendorSpecific;
    }

    private List getJdbcDriverLocations() {
	List jarFileLocations = new ArrayList();
        jarFileLocations.add(getLocation(SystemPropertyConstants.H2_ROOT_PROPERTY));
        jarFileLocations.add(getLocation(SystemPropertyConstants.INSTALL_ROOT_PROPERTY));
        jarFileLocations.add(getLocation(SystemPropertyConstants.INSTANCE_ROOT_PROPERTY));
        return jarFileLocations;
    }

    private File getLocation(String property) {
        return new File(System.getProperty(property) + File.separator + "lib");    
    }
    
    private static class JarFileFilter implements FilenameFilter {

        private static final String JAR_EXT = ".jar";

        public boolean accept(File dir, String name) {
            return name.endsWith(JAR_EXT);
        }
    }

    /**
     * Utility method that checks if a classname is vendor specific.
     * This method is used for jar files that do not have a manifest file to 
     * look up the classname.
     * @param dbVendor
     * @param className
     * @return true if className is vendor specific.
     */
    private boolean isVendorSpecific(String dbVendor, String className) {
        return className.toUpperCase(Locale.getDefault()).indexOf(
                dbVendor.toUpperCase(Locale.getDefault())) != -1;
    }

    /**
     * Get a vendor name from a Manifest entry in the file.
     * @param f
     * @return null if no manifest entry found.
     */
    private String getVendorFromManifest(File f) {
        String vendor = null;
        JarFile jarFile = null;
        try {
            jarFile = new JarFile(f);
            Manifest manifest = jarFile.getManifest();
            if(manifest != null) {
                Attributes mainAttributes = manifest.getMainAttributes();
                if(mainAttributes != null) {
                    vendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR.toString());
                    if (vendor == null) {
                        vendor = mainAttributes.getValue(Attributes.Name.IMPLEMENTATION_VENDOR_ID.toString());
                    }
                }
            }
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Exception while reading manifest file : ", ex);
        } finally {
            if (jarFile != null) {
                try {
                    jarFile.close();
                } catch (IOException ex) {
                    if (logger.isLoggable(Level.FINE)) {
                        logger.log(Level.FINE, "Exception while closing JarFile '"
                                + jarFile.getName() + "' :", ex);
                    }
                }
            }
        }
        return vendor;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy