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

org.nuiton.topia.persistence.internal.AbstractTopiaApplicationContext 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 com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.SessionFactory;
import org.hibernate.boot.Metadata;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.tool.schema.TargetType;
import org.nuiton.topia.persistence.TopiaApplicationContext;
import org.nuiton.topia.persistence.TopiaConfigurationBuilder;
import org.nuiton.topia.persistence.TopiaConfigurationExtension;
import org.nuiton.topia.persistence.TopiaDao;
import org.nuiton.topia.persistence.TopiaEntity;
import org.nuiton.topia.persistence.TopiaException;
import org.nuiton.topia.persistence.TopiaIdFactory;
import org.nuiton.topia.persistence.TopiaIdFactoryForBulkSupport;
import org.nuiton.topia.persistence.TopiaMigrationService;
import org.nuiton.topia.persistence.TopiaPersistenceContext;
import org.nuiton.topia.persistence.TopiaService;
import org.nuiton.topia.persistence.internal.support.TopiaServiceSupportImpl;
import org.nuiton.topia.persistence.jdbc.JdbcHelper;
import io.ultreia.java4all.util.sql.SqlScript;
import io.ultreia.java4all.util.sql.SqlScriptConsumer;
import io.ultreia.java4all.util.sql.SqlScriptReader;
import io.ultreia.java4all.util.sql.SqlScriptWriter;
import org.nuiton.topia.persistence.support.TopiaServiceSupport;
import org.nuiton.topia.persistence.util.TopiaUtil;

import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.function.Supplier;

/**
 * The application context is the main class in ToPIA usage. This class is a kind of equivalent of the RootTopiaContext.
 * It contains only high level methods and new contexts creation (transaction begin, ...). This class has to be extended
 * by the user, even if some default one could be automatically generated.
 *
 * @author Arnaud Thimel (Code Lutin)
 * @since 3.0
 */
public abstract class AbstractTopiaApplicationContext implements TopiaApplicationContext {

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

    public static final String MIGRATION_SERVICE_NAME = "migration";
    public static final String SQL_SERVICE_NAME = "sql";
    protected TopiaIdFactory topiaIdFactory;

    protected TopiaConfigurationExtension configuration;

    protected TopiaServiceSupport topiaServiceSupport;

    protected HibernateProvider hibernateProvider;

    protected TopiaHibernateSessionRegistry sessionRegistry;
    /**
     * Dao mapping.
     */
    private transient Map>> daoMapping;
    /**
     * Unique id associated with this application context.
     */
    protected final String authenticationToken = UUID.randomUUID().toString();
    /**
     * Set to {@code true} when at least one transaction has been done.
     */
    protected boolean open;

    protected boolean closed = false;

    /**
     * A set of known initialized opened persistence contexts. This set is mainly used to trigger shutdown on these
     * persistence contexts when the application context is closed.
     */
    protected Set persistenceContexts = Collections.newSetFromMap(
            new WeakHashMap<>());

    public AbstractTopiaApplicationContext(TopiaConfigurationExtension configuration) {
        this.configuration = configuration;
        init();
    }

    public Map>> getDaoMapping() {
        if (daoMapping == null) {
            daoMapping = createDaoMapping();
        }
        return daoMapping;
    }

    protected abstract Map>> createDaoMapping();

    public abstract TopiaIdFactoryForBulkSupport newIdFactoryForBulk(long timestamp);

    @Override
    public final String getAuthenticationToken() {
        return authenticationToken;
    }

    public String getPackageName() {
        return getClass().getPackageName();
    }

    protected void init() {

        new TopiaConfigurationBuilder().check(configuration);

        sessionRegistry = new TopiaHibernateSessionRegistry();

        // First initialize all the services
        initServices();

        // ToPIA's schema init
        if (isInitSchema()) {
            if (log.isInfoEnabled()) {
                log.info("Schema initialization enabled");
            }
            initSchema();
        } else {
            if (log.isInfoEnabled()) {
                log.info("Schema initialization disabled");
            }
        }

        // AThimel 14/06/14 Make sure this method is called AFTER ToPIA's schema init, otherwise Hibernate may have created the schema itself
        // The next line will trigger the Configuration#buildMappings() method which really initializes Hibernate
        getHibernateProvider().getHibernateConfiguration();

    }

    protected void initServices() {
        TopiaServiceSupportImpl topiaServiceSupportImpl = new TopiaServiceSupportImpl();
        this.topiaServiceSupport = topiaServiceSupportImpl;
        topiaServiceSupportImpl.initServices(this);
    }

    /**
     * If application context should init schema.
     * 

* This simple check is in its own method so it can be overridden. * * @return FIXME * @see org.nuiton.topia.persistence.TopiaConfiguration#isInitSchema() */ protected boolean isInitSchema() { return configuration.isInitSchema(); } /** * Will make everything possible to ensure the schema is ready to use. *

* It will create unless it already exists. If it already exists, we will try to find * a migration service and call it. */ @Override public void initSchema() { Collection migrationServices = getServices(TopiaMigrationService.class).values(); Preconditions.checkState(migrationServices.size() <= 1, "your configuration include multiple migration services: " + migrationServices); boolean migrationServiceEnabled = !migrationServices.isEmpty(); if (isSchemaEmpty()) { if (log.isInfoEnabled()) { log.info("schema is empty, will create"); } createSchema(); if (migrationServiceEnabled) { if (log.isInfoEnabled()) { log.info("schema created, will call migration service"); } TopiaMigrationService migrationService = Iterables.getOnlyElement(migrationServices); migrationService.initOnCreateSchema(); } else { if (log.isInfoEnabled()) { log.info("schema created, no migration service provided"); } } } else { if (migrationServiceEnabled) { if (log.isInfoEnabled()) { log.info("schema exists, will try to migrate"); } TopiaMigrationService migrationService = Iterables.getOnlyElement(migrationServices); migrationService.runSchemaMigration(); } else { if (log.isInfoEnabled()) { log.info("schema exists, no migration service provided"); } } } } protected abstract Set> getImplementationClasses(); protected void registerPersistenceContext(TopiaPersistenceContext persistenceContext) { persistenceContexts.add(persistenceContext); } // FIXME AThimel 25/11/13 I don't like it to be public, but necessary for services. Review it public HibernateProvider getHibernateProvider() { if (hibernateProvider == null) { // La liste des entités (ToPIA ou non) qu'Hibernate gère List> persistenceClasses = getPersistenceClasses(); hibernateProvider = new HibernateProvider(getConfiguration(), topiaServiceSupport, sessionRegistry, persistenceClasses); } return hibernateProvider; } @Override public TopiaConfigurationExtension getConfiguration() { return configuration; } protected TopiaIdFactory getTopiaIdFactory() { return getConfiguration().getTopiaIdFactory(); } public TopiaHibernateSessionRegistry getSessionRegistry() { return sessionRegistry; } @Override public Map getServices() { return topiaServiceSupport.getServices(); } @Override public Map getServices(Class interfaceService) { return topiaServiceSupport.getServices(interfaceService); } @Override public List> getPersistenceClasses() { // Par défaut la liste des entités qu'Hibernate gère est seulement la liste des entités ToPIA mais en // surchargeant cette méthode on peut ajouter des entités non ToPIA return new ArrayList<>(getImplementationClasses()); } @Override public boolean isSchemaEmpty() { // AThimel 14/06/14 getHibernateConfiguration() may create the schema, prefer using newHibernateConfiguration() which doesn't Configuration configuration = getHibernateProvider().newHibernateConfiguration(); configuration.getProperties().put(AvailableSettings.HBM2DDL_AUTO, "none"); try (SessionFactory sessionFactory = hibernateProvider.newSessionFactory(configuration)) { Metadata metaData = getHibernateProvider().newMetaData(configuration, sessionFactory); return TopiaUtil.isSchemaEmpty(configuration, metaData); } } @Override public boolean isTableExists(Class clazz) { // AThimel 14/06/14 getHibernateConfiguration() may create the schema, prefer using newHibernateConfiguration() which doesn't Configuration configuration = getHibernateProvider().getHibernateConfiguration(); Metadata metaData = getHibernateProvider().getMetaData(); return TopiaUtil.isSchemaExist(configuration, metaData, clazz.getName()); } @Override public String getSchemaName() { // TODO AThimel 02/08/13 I absolutely don't know if it works return getConfiguration().getSchemaName(); } @Override public void createSchema() { try { EnumSet targetTypes = EnumSet.of(TargetType.DATABASE); if (log.isDebugEnabled()) { targetTypes.add(TargetType.STDOUT); } Configuration configuration = getHibernateProvider().newHibernateConfiguration(); configuration.getProperties().remove(AvailableSettings.HBM2DDL_AUTO); try (SessionFactory sessionFactory = getHibernateProvider().newSessionFactory(configuration)) { Metadata metadata = getHibernateProvider().newMetaData(configuration, sessionFactory); new SchemaExport().execute(targetTypes, SchemaExport.Action.CREATE, metadata); } } catch (HibernateException eee) { throw new TopiaException(String.format("Could not create schema for reason: %s", eee.getMessage()), eee); } } @Override public void showCreateSchema() { try { new SchemaExport().execute(EnumSet.of(TargetType.DATABASE, TargetType.STDOUT), SchemaExport.Action.CREATE, getHibernateProvider().getMetaData()); } catch (HibernateException eee) { throw new TopiaException(String.format("Could not show create schema for reason: %s", eee.getMessage()), eee); } } @Override public void updateSchema() { try { EnumSet targetTypes = EnumSet.of(TargetType.DATABASE); if (log.isDebugEnabled()) { targetTypes.add(TargetType.STDOUT); } new SchemaUpdate().execute(targetTypes, getHibernateProvider().getMetaData()); } catch (HibernateException eee) { throw new TopiaException(String.format("Could not update schema for reason: %s", eee.getMessage()), eee); } } @Override public void dropSchema() { try { EnumSet targetTypes = EnumSet.of(TargetType.DATABASE); if (log.isDebugEnabled()) { targetTypes.add(TargetType.STDOUT); } new SchemaExport().execute(targetTypes, SchemaExport.Action.DROP, getHibernateProvider().getMetaData()); } catch (HibernateException eee) { throw new TopiaException(String.format("Could not drop schema for reason: %s", eee.getMessage()), eee); } } @Override public void close() { // Throw exception if context is already closed Preconditions.checkState(!closed, "TopiaApplicationContext was already closed"); if (log.isDebugEnabled()) { log.debug("will close " + this); } TopiaConfigurationExtension topiaConfiguration = getConfiguration(); if (topiaConfiguration.isH2Configuration()) { log.debug("Shutdown h2 database"); //FIXME Should we still do this ? // Fermer proprement la base JdbcHelper jdbcHelper = new JdbcHelper(topiaConfiguration); jdbcHelper.runUpdate("SHUTDOWN COMPACT;"); } // Iterate over the children PersistenceContexts and close them for (TopiaPersistenceContext persistenceContext : persistenceContexts) { if (persistenceContext == null) { if (log.isWarnEnabled()) { log.warn("null TopiaPersistenceContext found in #persistenceContexts"); } } else { // Avoid to have exception from checkNotClosed method on child try { if (!persistenceContext.isClosed()) { persistenceContext.close(); } } catch (Exception eee) { // Don't let any exception stop the application closing if (log.isWarnEnabled()) { log.warn("unable to close TopiaPersistenceContext", eee); } } } } hibernateProvider.close(); // call TopiaService#close on all services for (TopiaService topiaService : getServices().values()) { topiaService.close(); } closed = true; log.debug(this + " closed"); } @Override public ImmutableSet getSchemaNames() { ImmutableSet.Builder result = ImmutableSet.builder(); Collection classMappings = hibernateProvider.getMetaData().getEntityBindings(); for (PersistentClass persistentClass : classMappings) { String schema = persistentClass.getIdentityTable().getSchema(); if (StringUtils.isNotEmpty(schema)) { result.add(schema); } } return result.build(); } @Override public boolean isClosed() { return closed; } @Override public boolean isOpened() { return !isClosed(); } @Override public final boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AbstractTopiaApplicationContext)) return false; AbstractTopiaApplicationContext that = (AbstractTopiaApplicationContext) o; return Objects.equals(authenticationToken, that.authenticationToken); } @Override public final int hashCode() { return Objects.hash(authenticationToken); } public final boolean isOpen() { return isOpened() && open; } public final void setOpen(boolean open) { this.open = open; } public TopiaMigrationService getMigrationService() { return getServices(TopiaMigrationService.class).get(MIGRATION_SERVICE_NAME); } public final void migrate() { getMigrationService().runSchemaMigration(); } public final void backup(File backupFile) { backup(backupFile, true); } public final void backup(File backupFile, boolean compress) { if (!getConfiguration().isH2Configuration()) { throw new IllegalStateException("Cant backup a none H2 database."); } try (K p = newPersistenceContext()) { String sqlScript = String.format("SCRIPT NOPASSWORDS NOSETTINGS BLOCKSIZE 2048 TO '%s' %s CHARSET 'UTF-8';", backupFile.getAbsolutePath(), compress ? "COMPRESSION GZIP" : ""); p.getSqlSupport().executeSql(sqlScript); } } public final void executeSqlStatements(SqlScript content) { try { executeSqlStatements0(content); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new TopiaException(e); } } public final void executeSqlStatements(SqlScriptConsumer content) { try { executeSqlStatements0(content); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new TopiaException(e); } } protected final void executeSqlStatements0(SqlScript content) { SqlScriptReader scriptLocation = content.getLocation(); try (K persistenceContext = newPersistenceContext()) { persistenceContext.executeSqlScript(scriptLocation); persistenceContext.commit(); } } protected final void executeSqlStatements0(SqlScriptConsumer content) { try (K persistenceContext = newPersistenceContext()) { persistenceContext.executeSqlScript(content); persistenceContext.commit(); } } protected SqlScriptWriter newWriter(Path backupFile, boolean compress) { if (compress) { return SqlScriptWriter.builder(backupFile).keepCommentLine().keepEmptyLine().gzip().build(); } return SqlScriptWriter.builder(backupFile).keepCommentLine().keepEmptyLine().build(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy