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

org.nuiton.topia.persistence.internal.AbstractTopiaPersistenceContext 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 io.ultreia.java4all.lang.Strings;
import io.ultreia.java4all.util.TimeLog;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.Metadata;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Query;
import org.nuiton.topia.persistence.TopiaDao;
import org.nuiton.topia.persistence.TopiaDaoFactory;
import org.nuiton.topia.persistence.TopiaDaoFactoryImpl;
import org.nuiton.topia.persistence.TopiaEntity;
import org.nuiton.topia.persistence.TopiaException;
import org.nuiton.topia.persistence.TopiaIdFactory;
import org.nuiton.topia.persistence.TopiaPersistenceContext;
import org.nuiton.topia.persistence.internal.support.HibernateTopiaJpaSupport;
import org.nuiton.topia.persistence.internal.support.HibernateTopiaSqlSupport;
import io.ultreia.java4all.util.sql.SqlScriptConsumer;
import io.ultreia.java4all.util.sql.SqlScriptReader;
import io.ultreia.java4all.util.sql.SqlScript;
import org.nuiton.topia.persistence.support.QuerySupport;
import org.nuiton.topia.persistence.support.TopiaHibernateSupport;
import org.nuiton.topia.persistence.support.TopiaJpaSupport;
import org.nuiton.topia.persistence.support.TopiaSqlQuery;
import org.nuiton.topia.persistence.support.TopiaSqlSupport;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * Abstract implementation of the TopiaPersistenceContext. This class will be extended by a generated one in order to
 * generate getXxxDao methods.
 *
 * @author Arnaud Thimel (Code Lutin)
 * @since 3.0
 */
public abstract class AbstractTopiaPersistenceContext implements TopiaPersistenceContext, TopiaDaoFactory, QuerySupport {
    protected static final TimeLog TIME_LOG = new TimeLog(AbstractTopiaPersistenceContext.class, 500, 1000);
    private static final Logger log = LogManager.getLogger(AbstractTopiaPersistenceContext.class);
    /**
     * Already loaded DAO cache within this persistence context
     */
    private final TopiaDaoFactoryImpl daoCache;
    private Consumer onPreClose;


    /**
     * Used to affect a new topiaId when create is called.
     */
    protected TopiaIdFactory topiaIdFactory;

    /**
     * The shared instance of TopiaHibernateSessionRegistry
     */
    protected TopiaHibernateSessionRegistry sessionRegistry;

    /**
     * This subclass of TopiaHibernateSupport is made to be used only internally within this persistence context. This
     * instance is created by the persistence context itself.
     */
    protected InternalTopiaHibernateSupport hibernateSupport;

    /**
     * This instance of TopiaJpaSupport is created by the persistence context itself. It is actually using the
     * TopiaHibernateSupport instance.
     */
    protected TopiaJpaSupport jpaSupport;

    /**
     * This instance of TopiaSqlSupport is created by the persistence context itself. It is actually using the
     * TopiaHibernateSupport instance.
     */
    protected TopiaSqlSupport sqlSupport;

    /**
     * Flog used to check if this persistence context is closed
     */
    protected boolean closed = false;

    /**
     * Creating a new TopiaPersistenceContext is equivalent to creating a new transaction
     *
     * @param parameter everything needed (specific parameter-object)
     */
    public AbstractTopiaPersistenceContext(AbstractTopiaPersistenceContextConstructorParameter parameter) {
        this.topiaIdFactory = parameter.getTopiaIdFactory();
        this.sessionRegistry = parameter.getSessionRegistry();

        // Hibernate support can be created using the given hibernateProvider
        this.hibernateSupport = new InternalTopiaHibernateSupport(parameter.getHibernateProvider());

        // Now starts the transaction, as this persistenceContext IS the TopiaTransaction
        startTransaction();

        // Create the different supports that may be needed by the DAOs
        this.jpaSupport = new HibernateTopiaJpaSupport(hibernateSupport);
        this.sqlSupport = new HibernateTopiaSqlSupport(hibernateSupport);

        this.daoCache = new TopiaDaoFactoryImpl(parameter.getDaoMapping()) {
            @Override
            protected AbstractTopiaPersistenceContext persistenceContext() {
                return AbstractTopiaPersistenceContext.this;
            }
        };
    }

    public TopiaJpaSupport getJpaSupport() {
        return jpaSupport;
    }

    public TopiaHibernateSupport getHibernateSupport() {
        return hibernateSupport;
    }

    @Override
    public TopiaSqlSupport getSqlSupport() {
        return sqlSupport;
    }

    @Override
    public TopiaIdFactory getTopiaIdFactory() {
        return topiaIdFactory;
    }


    /**
     * This subclass of TopiaHibernateSupport is made to be used only internally within this persistence context. This
     * class only acts as an information container (as a structure does).
     */
    protected static class InternalTopiaHibernateSupport implements TopiaHibernateSupport {

        protected HibernateProvider hibernateProvider;
        protected Session hibernateSession;

        protected InternalTopiaHibernateSupport(HibernateProvider hibernateProvider) {
            this.hibernateProvider = hibernateProvider;
        }

        public void setHibernateSession(Session hibernateSession) {
            this.hibernateSession = hibernateSession;
        }

        @Override
        public Session getHibernateSession() {
            Preconditions.checkState(hibernateSession != null, "Session is not yet initialized");
            return hibernateSession;
        }

        @Override
        public SessionFactory getHibernateFactory() {
            return hibernateProvider.getSessionFactory();
        }

        @Override
        public Metadata getHibernateMetadata() {
            return hibernateProvider.getMetaData();
        }

        @Override
        public Configuration getHibernateConfiguration() {
            return hibernateProvider.getHibernateConfiguration();
        }
    }

    protected void startTransaction() throws TopiaException {

        SessionFactory factory = hibernateSupport.getHibernateFactory();
        Session result = factory.openSession();
        hibernateSupport.setHibernateSession(result);

        // Never synchronise the entity state (flush) until the user use ".commit()"
        result.setHibernateFlushMode(FlushMode.MANUAL);

        // tchemit 2010-12-06 propagates the value of the flag
//        result.useFlushMode = useFlushMode;

        sessionRegistry.register(result, this);

        // 20060926 poussin ajouter pour voir si ca regle les problemes de
        // deadlock h2. Conclusion, il faut bien ouvrir une transaction
        // maintenant, sinon lorsque l'on fait des acces a la base, une
        // transaction par defaut est utilisé mais elle n'est jamais vraiment
        // fermé ce qui pose des problemes de lock sur les tables.
        try {
            result.beginTransaction();
        } catch (Exception eee) {

            // Transaction may be opened: make sure it is closed then throw an exception
            try {
                result.close();
                sessionRegistry.unregister(result);
            } catch (HibernateException e1) {
                if (log.isErrorEnabled()) {
                    log.error("Could not close hibernate session", e1);
                }
            }

            String message = String.format("An error occurs while asking a new transaction: %1$s", eee.getMessage());
            throw new TopiaException(message, eee);
        }
    }

    protected void checkNotClosed() {
        Preconditions.checkState(!closed, "persistence context " + this + " is closed");
    }

    @Override
    public  E findByTopiaId(String topiaId) {
        checkNotClosed();

        Class entityClass = getTopiaIdFactory().getClassName(topiaId);
        TopiaDao dao = getDao(entityClass);
        return dao.forTopiaIdEquals(topiaId).findUnique();
    }

    @Override
    public void update(TopiaEntity entity) {
        checkNotClosed();

        String topiaId = entity.getTopiaId();
        Class entityClass = getTopiaIdFactory().getClassName(topiaId);
        TopiaDao dao = getDao(entityClass);
        dao.update(entity);

    }

    @Override
    public void delete(TopiaEntity entity) {
        checkNotClosed();

        String topiaId = entity.getTopiaId();
        Class entityClass = getTopiaIdFactory().getClassName(topiaId);
        TopiaDao dao = getDao(entityClass);
        dao.delete(entity);

    }

    @Override
    public  void deleteAll(Iterable entities) {
        for (E entity : entities) {
            delete(entity);
        }
    }

    @Override
    public  TopiaDao getDao(Class entityClass) {
        return daoCache.getDao(entityClass);
    }

    @Override
    public > D getDao(Class entityClass, Class daoClass) {
        TopiaDao dao = getDao(entityClass);
        return daoClass.cast(dao);
    }

    @Override
    public void commit() {
        checkNotClosed();

        try {

            Session hibernateSession = hibernateSupport.getHibernateSession();

            Transaction transaction = hibernateSession.getTransaction();
            hibernateSession.flush();
            transaction.commit();

            hibernateSession.beginTransaction();

        } catch (Exception eee) {
            String message = String.format("An error occurred during commit operation: %1$s", eee.getMessage());
            throw new TopiaException(message, eee);
        }
    }

    @Override
    public void rollback() {
        checkNotClosed();
        // Rollback and create a new transaction
        rollback0(true);
    }

    protected void rollback0(boolean beginAfterRollback) {
        try {
            Session hibernateSession = hibernateSupport.getHibernateSession();

            Transaction transaction = hibernateSession.getTransaction();
            hibernateSession.clear();
            transaction.rollback();
            hibernateSession.close();

            sessionRegistry.unregister(hibernateSession);

            if (beginAfterRollback) {
                // it's very important to change the session after rollback
                // otherwise there are many error during next Entity's modification
                hibernateSession = hibernateSupport.getHibernateFactory().openSession();
                hibernateSupport.setHibernateSession(hibernateSession);
                hibernateSession.setHibernateFlushMode(FlushMode.MANUAL);

                sessionRegistry.register(hibernateSession, this);

                hibernateSession.beginTransaction();
            }

        } catch (HibernateException eee) {
            String message = String.format("An error occurred during rollback operation: %1$s", eee.getMessage());
            throw new TopiaException(message, eee);
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    @Override
    public void close() {
        try {
            if (onPreClose != null) {
                onPreClose.accept(this);
            }
        } finally {
            try {

                checkNotClosed();

                if (log.isDebugEnabled()) {
                    log.debug("will close " + this);
                }

                closed = true;

                // Rollback and do not create a new transaction
                rollback0(false);

                // Now close the current Hibernate session
                Session hibernateSession = hibernateSupport.getHibernateSession();
                Preconditions.checkState(!hibernateSession.isOpen(), "Session should be closed after rollback0(false)");

                if (log.isDebugEnabled()) {
                    log.debug(this + " closed");
                }
            } finally {
                daoCache.close();
            }
        }

    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public  void setOnPreClose(Consumer onPreClose) {
        this.onPreClose = (Consumer) Objects.requireNonNull(onPreClose);
    }

    @Override
    public final void executeSqlScript(SqlScript content) {
        long t0 = TimeLog.getTime();
        executeSqlScript(SqlScriptConsumer.builder(content).batchSize(50).build());
        TIME_LOG.log(t0, "executeSqlScript", content.toString());
    }

    @Override
    public final void executeSqlScript(SqlScriptReader content) {
        long t0 = TimeLog.getTime();
        executeSqlScript(SqlScriptConsumer.builder(content).batchSize(50).build());
        TIME_LOG.log(t0, "executeSqlScript", content.toString());
    }

    @Override
    public final void executeSqlScript(SqlScriptConsumer content) {
        long t0 = TimeLog.getTime();
        getSqlSupport().doSqlWork(content);
        TIME_LOG.log(t0, "executeSqlScript", content.toString());
    }

    public final void flush() {
        getHibernateSupport().getHibernateSession().flush();
    }

    public final long countTable(String fullyTableName) {
        CountTableSqlWork countQuery = new CountTableSqlWork(fullyTableName);
        return getSqlSupport().findSingleResult(countQuery);
    }

    public final boolean exists(String topiaId) {
        checkNotClosed();
        Class entityClass = getTopiaIdFactory().getClassName(topiaId);
        TopiaDao dao = getDao(entityClass);
        return dao.forTopiaIdEquals(topiaId).exists();
    }

    @Override
    public final  Query getQuery(String queryName) {
        return getHibernateSupport().getQuery(queryName);
    }

    @Override
    public final  NativeQuery getSqlQuery(String queryName) {
        return getHibernateSupport().getSqlQuery(queryName);
    }

    public  boolean isTopiaId(Class entityClass, String str) {
        boolean isTopiaId = false;
        try {
            if (str != null) {
                String separator = getTopiaIdFactory().getSeparator();
                if (!str.endsWith(separator)) {
                    String[] split = str.split(separator);
                    if (split.length == 3) {
                        isTopiaId = Objects.equals(entityClass, getTopiaIdFactory().getClassName(str));
                        for (int index = 1; isTopiaId && index < split.length; index++) {
                            String part = split[index];
                            isTopiaId = !part.endsWith(".") && Strings.isNumeric(part.replace(".", ""));
                        }
                    }
                }
            }
        } catch (Exception e) {
            isTopiaId = false;
        }
        return isTopiaId;
    }

    public void doMapSqlWork(TopiaSqlQuery query) {
        sqlSupport.doSqlWork(connection -> {
            try (PreparedStatement preparedStatement = query.prepareQuery(connection)) {
                try (ResultSet resultSet = preparedStatement.executeQuery()) {
                    query.afterExecuteQuery(resultSet);
                    while (resultSet.next()) {
                        query.prepareResult(resultSet);
                    }
                }
            }
        });
    }

    protected static class CountTableSqlWork extends TopiaSqlQuery {

        private final String fullyTableName;

        CountTableSqlWork(String fullyTableName) {
            this.fullyTableName = fullyTableName;
        }

        @Override
        public PreparedStatement prepareQuery(Connection connection) throws SQLException {
            String sql = String.format("SELECT count(*) FROM %s", fullyTableName);
            return connection.prepareStatement(sql);
        }

        @Override
        public Long prepareResult(ResultSet set) throws SQLException {
            return set.getLong(1);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy