org.evolizer.core.hibernate.session.EvolizerSessionHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.evolizer.core.hibernate.session.EvolizerSessionHandler.java

Source

/*
 * Copyright 2009 University of Zurich, Switzerland
 *
 * Licensed 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.evolizer.core.hibernate.session;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.Platform;
import org.evolizer.core.exceptions.EvolizerException;
import org.evolizer.core.exceptions.EvolizerRuntimeException;
import org.evolizer.core.hibernate.EvolizerHibernatePlugin;
import org.evolizer.core.hibernate.model.api.IEvolizerModelEntity;
import org.evolizer.core.hibernate.model.api.IEvolizerModelProvider;
import org.evolizer.core.hibernate.session.api.IEvolizerSession;
import org.evolizer.core.hibernate.session.internal.EvolizerSessionImpl;
import org.evolizer.core.preferences.EvolizerPreferences;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;

/**
 * Handling of multiple Hibernate sessions using the ThreadLocal sessions.
 * 
 * Multiple Hibernate Sessions are managed by keeping a map of database url to session factory. There are two usage
 * patterns to obtain a session.
 * <ol>
 * <li>Via the Evolizer project properties for Eclipse Evolizer plugins.Using the properties, the current session of the
 * corresponding session factory is obtained. No precedent initialization of the session factory is needed.</li>
 * <li>Manually by providing the session database url as string or in a property object. In this case the session factory needs to be
 * initialized before.</li>
 * </ol>
 * 
 * Some suggestions: According to some articles on the Internet it is suggested to use one session per view. It does not
 * make sense to open and close sessions for each database access. You simply will get dozens of Hibernate exceptions,
 * e.g., because of lazy loading. Therefore, the session should be opened when the view is created and closed when the
 * view is closed.
 * 
 * @author pinzger
 * 
 */
public class EvolizerSessionHandler {

    private final static Logger sfLogger = EvolizerHibernatePlugin.getLogManager()
            .getLogger(EvolizerSessionHandler.class.getName());

    /**
     * Default Hibernate dialect
     */
    private final static String DEFAULT_DIALECT = "org.hibernate.dialect.MySQLDialect";
    /**
     * Default Hibernate database driver
     */
    private final static String DEFAULT_DRIVER = "com.mysql.jdbc.Driver";

    /**
     * Singleton of EvolizerSessionHandler
     */
    private static EvolizerSessionHandler fSessionHandler = null;

    /**
     * Map caching Hibernate session factories. There is one session factory per database.
     */
    private Hashtable<String, SessionFactory> fSessionFactoryMap = new Hashtable<String, SessionFactory>();

    /**
     * Map caching Hibernate sessions. Currently, we follow the strategy to have one session per database.
     */
    private Hashtable<String, IEvolizerSession> fSessionMap = new Hashtable<String, IEvolizerSession>();

    /**
     * Map of dbURL to Hibernate configuration properties. There is one configuration per database.
     */
    private Hashtable<String, Properties> fConnectionPropertiesMap = new Hashtable<String, Properties>();

    /**
     * Hidden default constructor.
     */
    private EvolizerSessionHandler() {
    }

    /**
     * Factory method creating the single instance of {@link EvolizerSessionHandler}.
     * 
     * @return The single instance of the current {@link EvolizerSessionHandler}
     */
    public static EvolizerSessionHandler getHandler() {
        if (fSessionHandler == null) {
            fSessionHandler = new EvolizerSessionHandler();
        }
        return fSessionHandler;
    }

    /**
     * Obtains the current session for the given the Url of the database. Keep in mind that ThreadLocal has to be
     * activated: current_session_context_class = "thread" This method can only be used after the Hibernate session has
     * been initialized.
     * 
     * @param dbUrl
     *            URL of the database in the form of <code>dbHost/dbName</code>, e.g.,
     *            <code>mysql://localhost:3306/evolizer_test</code>
     * @return the current Hibernate session.
     * @throws EvolizerException
     *             if the {@link IEvolizerSession} could not been initialized/obtained
     */
    public IEvolizerSession getCurrentSession(String dbUrl) throws EvolizerException {
        IEvolizerSession session = null;

        try {
            if (fSessionMap.containsKey(dbUrl) && fSessionMap.get(dbUrl).isOpen()) {
                session = fSessionMap.get(dbUrl);
            } else {
                if (getSessionFactory(dbUrl) != null) {
                    Session hibernateSession = getSessionFactory(dbUrl).openSession();
                    session = new EvolizerSessionImpl(hibernateSession);
                    fSessionMap.put(dbUrl, session);
                } else {
                    //                    sfLogger.error("Evolizer session factory for '" + dbUrl + "' has not been initialized.");
                    throw new EvolizerException(
                            "Evolizer session factory for '" + dbUrl + "' has not been initialized.");
                }

            }
        } catch (HibernateException he) {
            throw new EvolizerException(he);
        }

        return session;
    }

    /**
     * Obtains the Hibernate configuration from the project properties, initializes the Hibernate session factory and
     * returns the current Hibernate session.
     * 
     * @param project
     *            the Eclipse project containing Evolizer properties.
     * @return the current Hibernate session.
     * @throws EvolizerException
     *             if the {@link IEvolizerSession} could not been initialized/obtained
     * @see getCurrentSession(Properties)
     */
    public IEvolizerSession getCurrentSession(IProject project) throws EvolizerException {
        IEvolizerSession session = null;

        String dbUrl = getDBUrl(project);
        if (getSessionFactory(dbUrl) == null) {
            initSessionFactory(project);
        }

        session = getCurrentSession(dbUrl);
        return session;
    }

    /**
     * Initialize the Hibernate session factory from the given properties and
     * return the current Hibernate session. For the initialization the dbURL,
     * login, and password must be specified.
     *  
     * @param properties    The connection properties.
     * @return  The opened session.
     * @throws EvolizerException
     */
    public IEvolizerSession getCurrentSession(Properties properties) throws EvolizerException {
        IEvolizerSession session = null;

        if (properties.getProperty("hibernate.connection.url") != null) {
            String dbUrl = properties.getProperty("hibernate.connection.url");
            if (dbUrl.startsWith("jdbc:")) {
                dbUrl = dbUrl.substring("jdbc:".length());
            }
            if (getSessionFactory(dbUrl) == null) {
                initSessionFactory(properties);
            }

            session = getCurrentSession(dbUrl);
        } else {
            throw new EvolizerException("Properties must contain the 'hibernate.connection.url' entry");
        }

        return session;
    }

    /**
     * Initializes the Hibernate session factory with the given dbUrl, dbUser, and dbPassword. Each newly initializes
     * session factory is remembered in the session factory map.
     * 
     * @param dbUrl
     *            Url of the dabatase.
     * @param dbUser
     *            User of the database.
     * @param dbPassword
     *            Password of the user.
     * @throws EvolizerException
     *             if the {@link IEvolizerSession} could not been initialized/obtained
     */
    public void initSessionFactory(String dbUrl, String dbUser, String dbPassword) throws EvolizerException {
        Properties properties = getDefaultDBCofig(dbUrl, "", "", dbUser, dbPassword);
        initSessionFactory(properties);
    }

    /**
     * Initializes the Hibernate session factory with the Evolizer properties of the given project.
     * 
     * @param project   The project.
     * @throws EvolizerException
     */
    public void initSessionFactory(IProject project) throws EvolizerException {
        Properties properties = computeProperties(project);
        initSessionFactory(properties);
    }

    /**
     * Initializes the Hibernate session factory with the given options. Properties must contain an entry
     * for the database connection url.
     * 
     * @param properties  Hibernate configuration parameters
     * @throws EvolizerException  
     */
    public void initSessionFactory(Properties properties) throws EvolizerException {
        try {
            if (properties.getProperty("hibernate.connection.url") != null) {
                String dbUrl = properties.getProperty("hibernate.connection.url");
                if (dbUrl.startsWith("jdbc:")) {
                    dbUrl = dbUrl.substring("jdbc:".length());
                }
                if (!fSessionFactoryMap.containsKey(dbUrl)) {
                    AnnotationConfiguration configuration = configureDataBaseConnection(properties);
                    fSessionFactoryMap.put(dbUrl, configuration.buildSessionFactory());
                    fConnectionPropertiesMap.put(dbUrl, properties);
                }
            } else {
                throw new EvolizerException("Properties must contain the 'hibernate.connection.url' entry");
            }
        } catch (HibernateException he) {
            throw new EvolizerException(he);
        }
    }

    /**
     * Cleanup all open Hibernate sessions. Should be used when an application is closed.
     */
    public void cleanupHibernateSessions() {
        for (String dbUrl : fSessionMap.keySet()) {
            if (fSessionMap.get(dbUrl).isOpen()) {
                fSessionMap.get(dbUrl).close();
                fSessionMap.remove(dbUrl);
            }
        }

        cleanupHibernateSessionFactories();
    }

    /**
     * Cleanup open Hibernate session factories.
     */
    private void cleanupHibernateSessionFactories() {
        for (String dbUrl : fSessionFactoryMap.keySet()) {
            fSessionFactoryMap.get(dbUrl).close();
            fSessionFactoryMap.remove(dbUrl);
            fConnectionPropertiesMap.remove(dbUrl);
        }
    }

    /**
     * Helper function using the dbUrl to obtain the Hibernate session factory from the map. If the session factory has
     * not been initializes before, <code>null</code> is returned.
     * 
     * @param dbUrl
     *            Url (key) of the database.
     * @return the corresponding Hibernate session factory.
     * @throws EvolizerException
     *             if the {@link IEvolizerSession} could not been obtained
     */
    private SessionFactory getSessionFactory(String dbUrl) throws EvolizerException {
        SessionFactory sessionFactory = null;

        if (fSessionFactoryMap.containsKey(dbUrl)) {
            sessionFactory = fSessionFactoryMap.get(dbUrl);
        }

        return sessionFactory;
    }

    /**
     * Return the Hibernate properties for the given dbUrl.
     * 
     * @param dbUrl The database URL.
     * @return  The properties.
     */
    public Properties getProperties(String dbUrl) {
        Properties properties = null;
        if (fConnectionPropertiesMap.containsKey(dbUrl)) {
            properties = fConnectionPropertiesMap.get(dbUrl);
        }

        return properties;
    }

    /**
     * Create database schema.
     * 
     * @param project
     *            the project
     * @throws EvolizerException
     *             the evolizer exception
     * @see createSchema(Properties)
     */
    public void createSchema(IProject project) throws EvolizerException {
        createSchema(computeProperties(project));
    }

    /**
     * Update database schema.
     * 
     * @param project   The project.
     * @throws EvolizerException
     * @see updateSchema(Properties)
     */
    public void updateSchema(IProject project) throws EvolizerException {
        updateSchema(computeProperties(project));
    }

    /**
     * Drop database schema.
     * 
     * @param project   The project.
     * @throws EvolizerException
     * @see dropSchema(Properties)
     */
    public void dropSchema(IProject project) throws EvolizerException {
        dropSchema(computeProperties(project));
    }

    /**
     * Create database schema
     * 
     * @param dbUrl
     *            the db url
     * @param dbDialect
     *            the db dialect
     * @param dbDriverName
     *            the db driver name
     * @param dbUser
     *            the db user
     * @param dbPassword
     *            the db password
     * @throws EvolizerException
     *             if error occurs during o/r mapping
     * @see createSchema(Properties)
     */
    @Deprecated
    public void createSchema(String dbUrl, String dbDialect, String dbDriverName, String dbUser, String dbPassword)
            throws EvolizerException {
        createSchema(getDefaultDBCofig(dbUrl, dbDialect, dbDriverName, dbUser, dbPassword));
    }

    /**
     * Updates database schema.
     * 
     * @param dbUrl
     *            the db url
     * @param dbDialect
     *            the db dialect
     * @param dbDriverName
     *            the db driver name
     * @param dbUser
     *            the db user
     * @param dbPassword
     *            the db password
     * @throws EvolizerException
     * @see updateSchema(Properties)
     */
    @Deprecated
    public void updateSchema(String dbUrl, String dbDialect, String dbDriverName, String dbUser, String dbPassword)
            throws EvolizerException {
        updateSchema(getDefaultDBCofig(dbUrl, dbDialect, dbDriverName, dbUser, dbPassword));
    }

    /**
     * Drop the database schema.
     * 
     * @param dbUrl
     *            the db url
     * @param dbDialect
     *            the db dialect
     * @param dbDriverName
     *            the db driver name
     * @param dbUser
     *            the db user
     * @param dbPassword
     *            the db password
     * @throws EvolizerException
     * @see dropSchema(Properties)
     */
    @Deprecated
    public void dropSchema(String dbUrl, String dbDialect, String dbDriverName, String dbUser, String dbPassword)
            throws EvolizerException {
        dropSchema(getDefaultDBCofig(dbUrl, dbDialect, dbDriverName, dbUser, dbPassword));
    }

    /**
     * Creates the database schema based on the o/r mappings (e.g. the Hibernate/ejb3-annotations). Can only be executed
     * when session is closed.
     * 
     * @param properties    Database connection properties 
     * @throws EvolizerException
     */
    public void createSchema(Properties properties) throws EvolizerException {
        try {
            AnnotationConfiguration configuration = configureDataBaseConnection(properties);
            SchemaExport exporter = new SchemaExport(configuration);
            exporter.create(false, true);
        } catch (HibernateException he) {
            throw new EvolizerException(he);
        }
    }

    /**
     * Updates a database schema based on the o/r mappings (e.g. the Hibernate/ejb3-annotations). Can only be executed
     * when session is closed.
     *
     * @param properties    Database connection properties 
     * @throws EvolizerException
     */
    public void updateSchema(Properties properties) throws EvolizerException {
        try {
            AnnotationConfiguration configuration = configureDataBaseConnection(properties);
            SchemaUpdate updater = new SchemaUpdate(configuration);
            updater.execute(false, true);
        } catch (HibernateException he) {
            throw new EvolizerException(he);
        }
    }

    /**
     * Drops the database schema. Can only be executed when session is closed.
     * 
     * @param properties    Database connection properties 
     * @throws EvolizerException
     */
    public void dropSchema(Properties properties) throws EvolizerException {
        try {
            AnnotationConfiguration configuration = configureDataBaseConnection(properties);
            SchemaExport exporter = new SchemaExport(configuration);
            exporter.drop(false, true);
        } catch (HibernateException he) {
            throw new EvolizerException(he);
        }
    }

    /**
     * Creates the Hibernate configuration from the given properties.
     * 
     * @param properties
     *            the Hibernate properties
     * @return the Hinberate configuration.
     * @throws EvolizerException
     *             if this method fails. Reasons include:
     *             <ul>
     *             <li>The project does not exist.</li>
     *             <li>The project is not local.</li>
     *             <li>The project is a project that is not open.</li>
     *             </ul>
     * @throws EvolizerException
     */
    private AnnotationConfiguration configureDataBaseConnection(Properties properties) throws EvolizerException {
        AnnotationConfiguration configuration = new AnnotationConfiguration();

        try {
            configuration.setProperties(properties);

            for (Class<?> annotatedClass : gatherModels()) {
                configuration.addAnnotatedClass(annotatedClass);
                // sfLogger.debug("Added annotated class '" + annotatedClass.getCanonicalName() +
                // "' to configuration.");
            }
        } catch (MappingException e) {
            sfLogger.error("Error while mapping annotated classes " + e.getMessage(), e);
            throw new EvolizerException(e);
        }

        return configuration;
    }

    /**
     * Creates the Hibernate configuration for the given Eclipse project. Hibernate properties are obtained from the
     * project's Evolizer properties.
     * 
     * @param project
     *            the Eclipse project holding the Evolizer properties.
     * @return the Hinberate configuration.
     * @throws EvolizerException
     */
    private Properties computeProperties(IProject project) throws EvolizerException {
        Properties properties = new Properties();

        try {
            Boolean isInMemoryDBEnabled = false;
            if (project.getPersistentProperty(EvolizerPreferences.DB_USE_INMEMORY) != null) {
                isInMemoryDBEnabled = Boolean
                        .valueOf(project.getPersistentProperty(EvolizerPreferences.DB_USE_INMEMORY));
            }
            if (isInMemoryDBEnabled) {
                properties = getDefaultH2InMemoryConfig(project.getName());
            } else {
                String dbHost = project.getPersistentProperty(EvolizerPreferences.DB_HOST);
                String dbName = project.getPersistentProperty(EvolizerPreferences.DB_NAME);
                String dbUrl = dbHost + "/" + dbName;
                String dbUser = project.getPersistentProperty(EvolizerPreferences.DB_USER);
                String dbPassword = project.getPersistentProperty(EvolizerPreferences.DB_PASSWORD);

                properties.putAll(getDefaultDBCofig(dbUrl, "", "", dbUser, dbPassword));
            }
        } catch (CoreException e) {
            sfLogger.error("Error while fetching persistent properties from project '" + project.getName() + "'."
                    + e.getMessage(), e);
            throw new EvolizerException(e);
        }

        return properties;
    }

    /**
     * Returns the database URL from the given project. The project needs
     * the Evolizer properties to be defined.
     * 
     * @param project   The project.
     * @return  The database URL
     * @throws CoreException
     */
    public String getDBUrl(IProject project) throws EvolizerException {
        Properties properties = computeProperties(project);
        String dbUrl = properties.getProperty("hibernate.connection.url");
        if (dbUrl.startsWith("jdbc:")) {
            dbUrl = dbUrl.substring("jdbc:".length());
        }

        return dbUrl;
    }

    /**
     * Return the default configuration for an SQL-database connection. 
     * 
     * @param dbUrl The URL of the database
     * @param dbDialect The dialect
     * @param dbDriverName  The driver name
     * @param dbUser    The login name
     * @param dbPassword     The password.
     * @return  The default properties.
     */
    public static Properties getDefaultDBCofig(String dbUrl, String dbDialect, String dbDriverName, String dbUser,
            String dbPassword) {

        Properties properties = new Properties();

        if (dbDialect == "") {
            dbDialect = DEFAULT_DIALECT;
        }
        if (dbDriverName == "") {
            dbDriverName = DEFAULT_DRIVER;
        }

        properties.setProperty("hibernate.connection.url", "jdbc:" + dbUrl);
        properties.setProperty("hibernate.connection.username", dbUser);
        properties.setProperty("hibernate.connection.password", dbPassword);
        properties.setProperty("hibernate.dialect", dbDialect);
        properties.setProperty("hibernate.connection.driver_class", dbDriverName);

        properties.setProperty("hibernate.jdbc.batch_size", "25");
        properties.setProperty("hibernate.cache.use_second_level_cache", "false");

        // configuration.setProperty("hibernate.current_session_context_class", "thread");
        // configuration.setProperty("hibernate.current_session_context_class", "managed");

        // fHibernateAnnotationConfig.setProperty("hibernate.show_sql", "true");

        return properties;
    }

    /**
     * Returns the default configuration for the HSQLDB in-memory database.
     * 
     * @param dbName    The name of the database.
     * @return  The default properties.
     */
    public static Properties getDefaultHsqldbInMemoryConfig(final String dbName) {
        Properties properties = new Properties();

        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        properties.setProperty("hibernate.connection.driver_class", "org.hsqldb.jdbcDriver");
        properties.setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:" + dbName);
        properties.setProperty("hibernate.connection.username", "sa");
        properties.setProperty("hibernate.connection.password", "");
        properties.setProperty("hibernate.connection.pool_size", "1");
        properties.setProperty("hibernate.connection.autocommit", "true");
        //        properties.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider");
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");

        return properties;
    }

    /**
     * Returns the default configuration for the H2 in-memory database.
     * 
     * @param dbName    The name of the database
     * @return  The default properties
     */
    public static Properties getDefaultH2InMemoryConfig(final String dbName) {
        Properties properties = new Properties();

        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        properties.setProperty("hibernate.connection.driver_class", "org.h2.Driver");
        properties.setProperty("hibernate.connection.url", "jdbc:h2:mem:" + dbName);
        properties.setProperty("hibernate.connection.username", "sa");
        properties.setProperty("hibernate.connection.password", "sa");
        //        properties.setProperty("hibernate.connection.pool_size", "1");
        //        properties.setProperty("hibernate.connection.autocommit", "true");
        //        properties.setProperty("hibernate.cache.provider_class", "org.hibernate.cache.HashtableCacheProvider");
        properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");

        return properties;
    }

    /**
     * Queries all model providers and returns ejb3-annotated classes.
     * 
     * @return A list containing classes that are annotated with ejb3-tags for mapping.
     */
    private List<Class<?>> gatherModels() {
        List<Class<?>> annotatedClasses = new ArrayList<Class<?>>();

        // Iterate over all extensions and gather classes that are hibernate annotated
        IExtension[] extensions = Platform.getExtensionRegistry()
                .getExtensionPoint(EvolizerHibernatePlugin.PLUGIN_ID, "modelProvider").getExtensions();
        for (IExtension element : extensions) {
            IConfigurationElement[] configElements = element.getConfigurationElements();
            for (IConfigurationElement configElement : configElements) {
                try {
                    IEvolizerModelProvider provider = (IEvolizerModelProvider) configElement
                            .createExecutableExtension("class"); // Throws
                    // CoreException
                    // if executable
                    // could not be
                    // created.
                    Class<?>[] classes = provider.getAnnotatedClasses();
                    for (Class<?> clazz : classes) {
                        if (isModelEntity(clazz)) {
                            annotatedClasses.add(clazz);
                        } else {
                            throw new EvolizerRuntimeException(
                                    clazz.getSimpleName() + " does not implement IEvolizerModelEntity.");
                        }
                    }

                    sfLogger.debug("Added model " + configElement.getAttribute("name"));
                } catch (CoreException exception) {
                    String message = "Could not create executable extension from " + configElement.getContributor()
                            + ". " + exception.getMessage();

                    sfLogger.error(message, exception);

                    throw new EvolizerRuntimeException("Error while initializing log properties.", exception);
                }
            }
        }

        return annotatedClasses;
    }

    /**
     * Check whether the given class is an Evolizer model entity.
     * 
     * @param annotatedClass
     * @return
     */
    private boolean isModelEntity(Class<?> annotatedClass) {
        Class<?>[] interfaces = annotatedClass.getInterfaces();
        for (Class<?> interf : interfaces) {
            if (interf.equals(IEvolizerModelEntity.class)) {
                return true;
            }
        }

        Class<?> superClass = annotatedClass.getSuperclass();
        if (superClass.equals(Object.class)) {
            return false;
        } else {
            return isModelEntity(superClass);
        }
    }
}