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

org.nuiton.topia.persistence.internal.HibernateProvider Maven / Gradle / Ivy

The newest version!
package org.nuiton.topia.persistence.internal;

/*
 * #%L
 * ToPIA Extension :: API
 * %%
 * Copyright (C) 2018 - 2022 Ultreia.io
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataBuilder;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.spi.SessionFactoryOptions;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.dialect.DB2Dialect;
import org.hibernate.dialect.DerbyDialect;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.MySQL5Dialect;
import org.hibernate.dialect.Oracle10gDialect;
import org.hibernate.dialect.PostgreSQL9Dialect;
import org.hibernate.dialect.SQLServer2012Dialect;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.service.Service;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.service.spi.Stoppable;
import org.nuiton.topia.persistence.HibernateAvailableSettings;
import org.nuiton.topia.persistence.TopiaConfiguration;
import org.nuiton.topia.persistence.TopiaMisconfigurationException;
import org.nuiton.topia.persistence.jdbc.JdbcConfigurationBuilder;
import org.nuiton.topia.persistence.support.TopiaServiceSupport;
import org.nuiton.topia.persistence.util.TopiaUtil;

import java.util.Collection;
import java.util.Properties;
import java.util.Set;

/**
 * @author Arnaud Thimel (Code Lutin)
 */
public class HibernateProvider {

    private static final Logger log = LogManager.getLogger(HibernateProvider.class);

    static final String HIKARI_CP_HIBERNATE_CONNECTION_PROVIDER_CLASS_NAME = "org.hibernate.hikaricp.internal.HikariCPConnectionProvider";

    static final String C3P0_HIBERNATE_CONNECTION_PROVIDER_CLASS_NAME = "org.hibernate.c3p0.internal.C3P0ConnectionProvider";

    protected SessionFactory hibernateSessionFactory;

    protected Configuration hibernateConfiguration;

    protected TopiaConfiguration topiaConfiguration;

    protected TopiaServiceSupport topiaServiceSupport;

    protected TopiaHibernateSessionRegistry sessionRegistry;

    /**
     * List of persistent classes (TopiaEntity or not)
     */
    protected Set> persistenceClasses;

    protected Metadata metaData;

    public HibernateProvider(TopiaConfiguration topiaConfiguration,
                             TopiaServiceSupport topiaServiceSupport,
                             TopiaHibernateSessionRegistry sessionRegistry,
                             Iterable> persistenceClasses) {
        this.topiaConfiguration = topiaConfiguration;
        this.topiaServiceSupport = topiaServiceSupport;
        this.sessionRegistry = sessionRegistry;
        this.persistenceClasses = ImmutableSet.copyOf(persistenceClasses);
    }

    /**
     * Get the current Hibernate Configuration. The Configuration instance is lazy-initialized using the
     * {@link #newHibernateConfiguration()} method. The returned instance is always initialized and mapping are built.
     *
     * @return the Hibernate Configuration instance with built mappings.
     */
    public Configuration getHibernateConfiguration() {
        if (hibernateConfiguration == null) {
            hibernateConfiguration = newHibernateConfiguration();

            TopiaUtil.warnOnAutomaticSchemaOperationRisk(hibernateConfiguration);
        }
        return hibernateConfiguration;
    }

    /**
     * Creates a new Configuration instance. The instance is create but mappings are not built yet to avoid unwanted
     * database access.
     *
     * @return a new Hibernate Configuration instance without built mappings.
     */
    public Configuration newHibernateConfiguration() {

        Configuration newHibernateConfiguration = new Configuration();

        for (Class entityClass : persistenceClasses) {
            newHibernateConfiguration.addClass(entityClass);
        }

        Properties properties = new Properties();

        // JDBC
        properties.put(AvailableSettings.URL, topiaConfiguration.getJdbcConnectionUrl());
        properties.put(AvailableSettings.DRIVER, topiaConfiguration.getJdbcDriverClass().getName());
        properties.put(AvailableSettings.USER, topiaConfiguration.getJdbcConnectionUser());
        properties.put(AvailableSettings.PASS, topiaConfiguration.getJdbcConnectionPassword());

        // dialect
        properties.put(AvailableSettings.DIALECT, getHibernateDialect(topiaConfiguration));

        // using c3p0 with default configuration
        if (topiaConfiguration.isUseHikariForJdbcConnectionPooling()) {
            properties.put(AvailableSettings.CONNECTION_PROVIDER, HIKARI_CP_HIBERNATE_CONNECTION_PROVIDER_CLASS_NAME);
        } else {
            properties.put(AvailableSettings.CONNECTION_PROVIDER, C3P0_HIBERNATE_CONNECTION_PROVIDER_CLASS_NAME);
            properties.put(AvailableSettings.C3P0_MIN_SIZE, 5);
            properties.put(AvailableSettings.C3P0_MAX_SIZE, 20);
            properties.put(AvailableSettings.C3P0_TIMEOUT, 1800);
            properties.put(AvailableSettings.C3P0_MAX_STATEMENTS, 50);
        }

        // schema validation
        if (topiaConfiguration.isValidateSchema()) {
            properties.put(AvailableSettings.HBM2DDL_AUTO, "validate");
        }

        properties.put(HibernateAvailableSettings.NAMING_STRATEGY, org.hibernate.cfg.ImprovedNamingStrategy.class.getName());
        properties.put(AvailableSettings.FORMAT_SQL, true);
        properties.put(AvailableSettings.USE_SQL_COMMENTS, true);

        // user specific configuration
        properties.putAll(topiaConfiguration.getHibernateExtraConfiguration());

        if (log.isInfoEnabled()) {
            log.info("will start hibernate with configuration " + properties);
        }

        newHibernateConfiguration.setProperties(properties);

        return newHibernateConfiguration;

    }

    /**
     * Get Hibernate {@link org.hibernate.dialect.Dialect} to use for given {@link TopiaConfiguration}.
     * 

* Prefer user defined dialect over dialect guessed by ToPIA; * Warn user if dialect declared seems wrong (H2 dialect for a PostgreSQL database) * * @throws TopiaMisconfigurationException if user must add dialect to its configuration (because it can not be guessed) */ public static String getHibernateDialect(TopiaConfiguration topiaConfiguration) { String jdbcConnectionUrl = topiaConfiguration.getJdbcConnectionUrl(); String guessedDialect = guessHibernateDialect(jdbcConnectionUrl); String userDefinedDialect = topiaConfiguration.getHibernateExtraConfiguration().get(AvailableSettings.DIALECT); String dialect; if (guessedDialect == null) { if (userDefinedDialect == null) { String message = String.format( "unable to guess Hibernate dialect to use for JDBC URL %s please patch ToPIA or configure Hibernate manually using %s", jdbcConnectionUrl, HibernateAvailableSettings.DIALECT); throw new TopiaMisconfigurationException(message, topiaConfiguration); } else { dialect = userDefinedDialect; } } else { if (userDefinedDialect == null) { dialect = guessedDialect; } else { dialect = userDefinedDialect; if (guessedDialect.equals(userDefinedDialect)) { log.info("configuration defined hibernate dialect " + userDefinedDialect + " but ToPIA could have guessed it (you can remove the configuration directive safely)"); } else { log.warn("not sure if " + dialect + " is suitable for " + jdbcConnectionUrl); } } } return dialect; } /** * @deprecated Hibernate can do a much better job at guessing the dialect by calling JDBC and discovering the actual DBMS version and the dialect to use. */ @Deprecated public static String guessHibernateDialect(String jdbcConnectionUrl) { JdbcConfigurationBuilder jdbcConfigurationBuilder = new JdbcConfigurationBuilder(); String guessedHibernateDialect = null; // DB2 if (jdbcConfigurationBuilder.isDb2Url(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=DB2")) { guessedHibernateDialect = DB2Dialect.class.getName(); // Derby } else if (jdbcConfigurationBuilder.isDerbyUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=Derby")) { guessedHibernateDialect = DerbyDialect.class.getName(); // HSQLDB } else if (jdbcConfigurationBuilder.isHsqlDbUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=HSQLDB")) { guessedHibernateDialect = HSQLDialect.class.getName(); // MySQL } else if (jdbcConfigurationBuilder.isMysqlUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isMariaDbUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isGoogleAppEngineUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=MySQL")) { guessedHibernateDialect = MySQL5Dialect.class.getName(); // Oracle } else if (jdbcConfigurationBuilder.isOracleUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=Oracle")) { guessedHibernateDialect = Oracle10gDialect.class.getName(); // PostgreSQL } else if (jdbcConfigurationBuilder.isPostgreSqlUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=PostgreSQL")) { guessedHibernateDialect = PostgreSQL9Dialect.class.getName(); // MS SQLServer } else if (jdbcConfigurationBuilder.isJtdsUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isSqlServerUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl) && jdbcConnectionUrl.contains("MODE=MSSQLServer")) { guessedHibernateDialect = SQLServer2012Dialect.class.getName(); // SQLite } else if (jdbcConfigurationBuilder.isSqliteUrl(jdbcConnectionUrl) || jdbcConfigurationBuilder.isSqlDroidUrl(jdbcConnectionUrl)) { guessedHibernateDialect = null; // H2 } else if (jdbcConfigurationBuilder.isH2Url(jdbcConnectionUrl)) { guessedHibernateDialect = H2Dialect.class.getName(); } else { if (log.isWarnEnabled()) { log.warn("unable to guess Hibernate dialect for JDBC URL " + jdbcConnectionUrl + ". Please, submit a patch!"); } } return guessedHibernateDialect; } /** * Method to extract from the given Hibernate SessionFactory a working instance of StandardServiceRegistry *

* IMPORTANT : As much as possible, prefer using the * {@link #getSessionFactoryServiceRegistry(org.hibernate.SessionFactory)} mthod instead of the current one because * the SessionFactoryServiceRegistry is a child of the StandardServiceRegistry *

* NB: This method is static to make sure it does not depend on the current instance * * @param sessionFactory the Hibernate's SessionFactory instance * @return the StandardServiceRegistry instance used by the given SessionFactory */ public static StandardServiceRegistry getStandardServiceRegistry(SessionFactory sessionFactory) { // AThimel 03/04/14 The next two lines are the good way to get the StandardServiceRegistry in Hibernate 4.3 SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions(); return sessionFactoryOptions.getServiceRegistry(); } /** * Method to extract from the given Hibernate SessionFactory a working instance of SessionFactoryServiceRegistry *

* IMPORTANT : If possible, prefer using this method instead of * {@link #getStandardServiceRegistry(org.hibernate.SessionFactory)} because the SessionFactoryServiceRegistry is a * child of the StandardServiceRegistry *

* NB: This method is static to make sure it does not depend on the current instance * * @param sessionFactory the Hibernate's SessionFactory instance * @return the SessionFactoryServiceRegistry instance used by the given SessionFactory */ protected static SessionFactoryServiceRegistry getSessionFactoryServiceRegistry(SessionFactory sessionFactory) { // AThimel 03/04/14 The next two lines are the good way to get the SessionFactoryServiceRegistry in Hibernate 4.3 SessionFactoryImplementor sessionFactoryImplementor = (SessionFactoryImplementor) sessionFactory; return (SessionFactoryServiceRegistry) sessionFactoryImplementor.getServiceRegistry(); } /** * Method to get an Hibernate service instance from a given Hibernate SessionFactory *

* NB: This method is static to make sure it does not depend on the current instance * * @param sessionFactory the Hibernate's SessionFactory instance * @param serviceClass the expected service class * @param type of service * @return the found service instance * @throws org.hibernate.service.UnknownServiceException Indicates the service was not known. * @see org.hibernate.service.ServiceRegistry#getService(Class) */ public static S getHibernateService(SessionFactory sessionFactory, Class serviceClass) { // Hibernate 4.3.x : prefer using the SessionFactoryServiceRegistry method instead of StandardServiceRegistry // because SessionFactoryServiceRegistry is a child of the StandardServiceRegistry ServiceRegistry serviceRegistry = getSessionFactoryServiceRegistry(sessionFactory); return serviceRegistry.getService(serviceClass); } public void close() { metaData = null; if (hibernateSessionFactory != null) { Preconditions.checkState(!hibernateSessionFactory.isClosed()); // close connection provider if possible (http://nuiton.org/issues/2757) ConnectionProvider service = getHibernateService(hibernateSessionFactory, ConnectionProvider.class); if (service instanceof Stoppable) { Stoppable stoppable = (Stoppable) service; stoppable.stop(); } hibernateSessionFactory.close(); } } public SessionFactory getSessionFactory() { if (hibernateSessionFactory == null) { Configuration effectiveHibernateConfiguration = getHibernateConfiguration(); hibernateSessionFactory = TopiaUtil.newSessionFactory(effectiveHibernateConfiguration); } return hibernateSessionFactory; } public SessionFactory newSessionFactory(Configuration effectiveHibernateConfiguration) { return TopiaUtil.newSessionFactory(effectiveHibernateConfiguration); } public Metadata getMetaData() { if (metaData == null) { metaData = newMetaData(getHibernateConfiguration(), getSessionFactory()); } return metaData; } public Metadata newMetaData(Configuration configuration, SessionFactory sessionFactory) { return newMetaData(configuration, sessionFactory, persistenceClasses); } public Metadata newMetaData(Configuration configuration, SessionFactory sessionFactory, Collection> persistenceClasses) { StandardServiceRegistry standardServiceRegistry = getStandardServiceRegistry(sessionFactory); MetadataSources sources = new MetadataSources(standardServiceRegistry); for (Class persistanceClass : persistenceClasses) { String hbmXmlFile = persistanceClass.getName().replace('.', '/') + ".hbm.xml"; sources.addResource(hbmXmlFile); configuration.addClass(persistanceClass); } MetadataBuilder metadataBuilder = sources.getMetadataBuilder(); return metadataBuilder.build(); } }