
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 extends TopiaEntity> 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