Fix JPA Transaction#commit() does not throw RollbacackException
This commit is contained in:
parent
3637bd342d
commit
4f1bca75bc
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.engine.spi;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.LockOptions;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
public interface ExceptionConverter {
|
||||
/**
|
||||
* Converts the exception thrown during the transaction commit phase
|
||||
*
|
||||
* @param e The exception being handled
|
||||
*
|
||||
* @return The converted exception
|
||||
*/
|
||||
RuntimeException convertCommitException(RuntimeException e);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
* @param lockOptions The lock options in effect at the time of exception (can be null)
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e, LockOptions lockOptions);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e);
|
||||
|
||||
RuntimeException convert(RuntimeException e);
|
||||
|
||||
RuntimeException convert(RuntimeException e, LockOptions lockOptions);
|
||||
|
||||
JDBCException convert(SQLException e, String message);
|
||||
}
|
|
@ -17,7 +17,6 @@ import javax.persistence.EntityGraph;
|
|||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaDelete;
|
||||
|
@ -145,41 +144,6 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
|
|||
return delegate.isTransactionInProgress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlePersistenceException(PersistenceException e) {
|
||||
delegate.handlePersistenceException( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void throwPersistenceException(PersistenceException e) {
|
||||
delegate.throwPersistenceException( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e, LockOptions lockOptions) {
|
||||
return delegate.convert( e, lockOptions );
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(RuntimeException e) {
|
||||
return delegate.convert( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e) {
|
||||
return delegate.convert( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void throwPersistenceException(HibernateException e) {
|
||||
delegate.throwPersistenceException( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException wrapStaleStateException(StaleStateException e) {
|
||||
return delegate.wrapStaleStateException( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockOptions getLockRequest(LockModeType lockModeType, Map<String, Object> properties) {
|
||||
return delegate.getLockRequest( lockModeType, properties );
|
||||
|
@ -459,6 +423,11 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
|
|||
return delegate.getLoadQueryInfluencers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExceptionConverter getExceptionConverter() {
|
||||
return delegate.getExceptionConverter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionEventListenerManager getEventListenerManager() {
|
||||
return delegate.getEventListenerManager();
|
||||
|
|
|
@ -12,18 +12,15 @@ import java.util.Iterator;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.Criteria;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Interceptor;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.ScrollMode;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.SharedSessionContract;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.engine.jdbc.LobCreationContext;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
|
||||
|
@ -142,57 +139,6 @@ public interface SharedSessionContractImplementor
|
|||
*/
|
||||
void markForRollbackOnly();
|
||||
|
||||
/**
|
||||
* Handles marking for rollback and other such operations that need to occur depending on the type of
|
||||
* exception being handled.
|
||||
*
|
||||
* @param e The exception being handled.
|
||||
*/
|
||||
void handlePersistenceException(PersistenceException e);
|
||||
|
||||
/**
|
||||
* Delegates to {@link #handlePersistenceException} and then throws the given exception.
|
||||
*
|
||||
* @param e The exception being handled and finally thrown.
|
||||
*/
|
||||
void throwPersistenceException(PersistenceException e);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
* Any appropriate/needed calls to {@link #handlePersistenceException} are also made.
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
* @param lockOptions The lock options in effect at the time of exception (can be null)
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e, LockOptions lockOptions);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
* Any appropriate/needed calls to {@link #handlePersistenceException} are also made.
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e);
|
||||
|
||||
RuntimeException convert(RuntimeException e);
|
||||
|
||||
/**
|
||||
* Delegates to {@link #convert} and then throws the given exception.
|
||||
*
|
||||
* @param e The exception being handled and finally thrown.
|
||||
*/
|
||||
void throwPersistenceException(HibernateException e);
|
||||
|
||||
PersistenceException wrapStaleStateException(StaleStateException e);
|
||||
|
||||
/**
|
||||
* System time beforeQuery the start of the transaction
|
||||
*/
|
||||
|
@ -451,4 +397,6 @@ public interface SharedSessionContractImplementor
|
|||
* should never be null.
|
||||
*/
|
||||
LoadQueryInfluencers getLoadQueryInfluencers();
|
||||
|
||||
ExceptionConverter getExceptionConverter();
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import javax.transaction.Synchronization;
|
|||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.TransactionException;
|
||||
import org.hibernate.engine.spi.ExceptionConverter;
|
||||
import org.hibernate.engine.transaction.spi.TransactionImplementor;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.resource.transaction.spi.TransactionCoordinator;
|
||||
|
@ -27,10 +28,12 @@ public class TransactionImpl implements TransactionImplementor {
|
|||
private static final Logger LOG = CoreLogging.logger( TransactionImpl.class );
|
||||
|
||||
private final TransactionCoordinator transactionCoordinator;
|
||||
private final ExceptionConverter exceptionConverter;
|
||||
private TransactionDriver transactionDriverControl;
|
||||
|
||||
public TransactionImpl(TransactionCoordinator transactionCoordinator) {
|
||||
public TransactionImpl(TransactionCoordinator transactionCoordinator, ExceptionConverter exceptionConverter) {
|
||||
this.transactionCoordinator = transactionCoordinator;
|
||||
this.exceptionConverter = exceptionConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -60,9 +63,13 @@ public class TransactionImpl implements TransactionImplementor {
|
|||
}
|
||||
|
||||
LOG.debug( "committing" );
|
||||
|
||||
try {
|
||||
internalGetTransactionDriverControl().commit();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw exceptionConverter.convertCommitException( e );
|
||||
}
|
||||
}
|
||||
|
||||
public TransactionDriver internalGetTransactionDriverControl() {
|
||||
// NOTE here to help be a more descriptive NullPointerException
|
||||
|
|
|
@ -13,16 +13,7 @@ import java.io.Serializable;
|
|||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.EntityExistsException;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockTimeoutException;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.PessimisticLockException;
|
||||
import javax.persistence.QueryTimeoutException;
|
||||
import javax.persistence.Tuple;
|
||||
|
||||
import org.hibernate.AssertionFailure;
|
||||
|
@ -33,27 +24,15 @@ import org.hibernate.FlushMode;
|
|||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.Interceptor;
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.MultiTenancyStrategy;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.ScrollableResults;
|
||||
import org.hibernate.SessionException;
|
||||
import org.hibernate.StaleObjectStateException;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.TransactionException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.UnresolvableObjectException;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
|
||||
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.dialect.lock.LockingStrategyException;
|
||||
import org.hibernate.dialect.lock.OptimisticEntityLockException;
|
||||
import org.hibernate.dialect.lock.PessimisticEntityLockException;
|
||||
import org.hibernate.engine.ResultSetMappingDefinition;
|
||||
import org.hibernate.engine.internal.SessionEventListenerManagerImpl;
|
||||
import org.hibernate.engine.jdbc.LobCreationContext;
|
||||
|
@ -71,6 +50,7 @@ import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
|
|||
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
|
||||
import org.hibernate.engine.query.spi.sql.NativeSQLQuerySpecification;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.ExceptionConverter;
|
||||
import org.hibernate.engine.spi.NamedQueryDefinition;
|
||||
import org.hibernate.engine.spi.NamedSQLQueryDefinition;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
|
@ -147,6 +127,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
private transient Boolean useStreamForLobBinding;
|
||||
private transient long timestamp;
|
||||
|
||||
protected transient ExceptionConverter exceptionConverter;
|
||||
|
||||
public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) {
|
||||
this.factory = factory;
|
||||
this.sessionIdentifier = StandardRandomStrategy.INSTANCE.generateUUID( null );
|
||||
|
@ -212,6 +194,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
.getService( TransactionCoordinatorBuilder.class )
|
||||
.buildTransactionCoordinator( jdbcCoordinator, this );
|
||||
}
|
||||
exceptionConverter = new ExceptionConverterImpl( this );
|
||||
}
|
||||
|
||||
protected void addSharedSessionTransactionObserver(TransactionCoordinator transactionCoordinator) {
|
||||
|
@ -377,7 +360,10 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
}
|
||||
|
||||
if ( this.currentHibernateTransaction == null ) {
|
||||
this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator() );
|
||||
this.currentHibernateTransaction = new TransactionImpl(
|
||||
getTransactionCoordinator(),
|
||||
getExceptionConverter()
|
||||
);
|
||||
}
|
||||
if ( !isClosed() ) {
|
||||
getTransactionCoordinator().pulse();
|
||||
|
@ -389,7 +375,10 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
checkOpen();
|
||||
|
||||
if ( this.currentHibernateTransaction == null || this.currentHibernateTransaction.getStatus() != TransactionStatus.ACTIVE ) {
|
||||
this.currentHibernateTransaction = new TransactionImpl( getTransactionCoordinator() );
|
||||
this.currentHibernateTransaction = new TransactionImpl(
|
||||
getTransactionCoordinator(),
|
||||
getExceptionConverter()
|
||||
);
|
||||
}
|
||||
getTransactionCoordinator().pulse();
|
||||
return currentHibernateTransaction;
|
||||
|
@ -484,7 +473,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return callback.executeOnConnection( connection );
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw convert(
|
||||
throw exceptionConverter.convert(
|
||||
e,
|
||||
"Error creating contextual LOB : " + e.getMessage()
|
||||
);
|
||||
|
@ -565,7 +554,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return createNativeQuery( nativeQueryDefinition );
|
||||
}
|
||||
|
||||
throw convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
}
|
||||
|
||||
protected QueryImplementor createQuery(NamedQueryDefinition queryDefinition) {
|
||||
|
@ -650,7 +639,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return query;
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -671,7 +660,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return query;
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -748,7 +737,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return (QueryImplementor<T>) createNativeQuery( nativeQueryDefinition, resultType );
|
||||
}
|
||||
|
||||
throw convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "unchecked"})
|
||||
|
@ -859,7 +848,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return query;
|
||||
}
|
||||
catch ( RuntimeException he ) {
|
||||
throw convert( he );
|
||||
throw exceptionConverter.convert( he );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -875,7 +864,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return query;
|
||||
}
|
||||
catch ( RuntimeException he ) {
|
||||
throw convert( he );
|
||||
throw exceptionConverter.convert( he );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -891,7 +880,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return query;
|
||||
}
|
||||
catch ( RuntimeException he ) {
|
||||
throw convert( he );
|
||||
throw exceptionConverter.convert( he );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -906,7 +895,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return createNativeQuery( nativeQueryDefinition );
|
||||
}
|
||||
|
||||
throw convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -962,193 +951,6 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return procedureCall;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected JDBCException convert(SQLException e, String message) {
|
||||
return getJdbcServices().getSqlExceptionHelper().convert( e, message );
|
||||
}
|
||||
|
||||
public RuntimeException convert(HibernateException e) {
|
||||
return convert( e, null );
|
||||
}
|
||||
|
||||
public RuntimeException convert(RuntimeException e) {
|
||||
RuntimeException result = e;
|
||||
if ( e instanceof HibernateException ) {
|
||||
result = convert( (HibernateException) e );
|
||||
}
|
||||
else {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void handlePersistenceException(PersistenceException e) {
|
||||
if ( e instanceof NoResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof NonUniqueResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof LockTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof QueryTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
catch ( Exception ne ) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnPersistenceException(ne);
|
||||
}
|
||||
}
|
||||
|
||||
public void throwPersistenceException(PersistenceException e) {
|
||||
throw convert( e );
|
||||
}
|
||||
|
||||
public void throwPersistenceException(HibernateException e) {
|
||||
throw convert( e );
|
||||
}
|
||||
|
||||
public RuntimeException convert(HibernateException e, LockOptions lockOptions) {
|
||||
Throwable cause = e;
|
||||
if(e instanceof TransactionException ){
|
||||
cause = e.getCause();
|
||||
}
|
||||
if ( cause instanceof StaleStateException ) {
|
||||
final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof LockingStrategyException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.PessimisticLockException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.QueryTimeoutException ) {
|
||||
final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof ObjectNotFoundException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueObjectException ) {
|
||||
final EntityExistsException converted = new EntityExistsException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueResultException ) {
|
||||
final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof UnresolvableObjectException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof QueryException ) {
|
||||
return new IllegalArgumentException( cause );
|
||||
}
|
||||
else if ( cause instanceof TransientObjectException ) {
|
||||
try {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
catch ( Exception ne ) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnTransientObjectException( ne );
|
||||
}
|
||||
return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules
|
||||
}
|
||||
else {
|
||||
final PersistenceException converted = new PersistenceException( cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
|
||||
public PersistenceException wrapLockException(HibernateException e, LockOptions lockOptions) {
|
||||
final PersistenceException pe;
|
||||
if ( e instanceof OptimisticEntityLockException ) {
|
||||
final OptimisticEntityLockException lockException = (OptimisticEntityLockException) e;
|
||||
pe = new OptimisticLockException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else if ( e instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
pe = new LockTimeoutException( e.getMessage(), e, null );
|
||||
}
|
||||
else if ( e instanceof PessimisticEntityLockException ) {
|
||||
final PessimisticEntityLockException lockException = (PessimisticEntityLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
}
|
||||
else if ( e instanceof org.hibernate.PessimisticLockException ) {
|
||||
final org.hibernate.PessimisticLockException jdbcLockException = (org.hibernate.PessimisticLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
public PersistenceException wrapStaleStateException(StaleStateException e) {
|
||||
PersistenceException pe;
|
||||
if ( e instanceof StaleObjectStateException ) {
|
||||
final StaleObjectStateException sose = (StaleObjectStateException) e;
|
||||
final Serializable identifier = sose.getIdentifier();
|
||||
if ( identifier != null ) {
|
||||
try {
|
||||
final Object entity = load( sose.getEntityName(), identifier );
|
||||
if ( entity instanceof Serializable ) {
|
||||
//avoid some user errors regarding boundary crossing
|
||||
pe = new OptimisticLockException( e.getMessage(), e, entity );
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
catch ( EntityNotFoundException enfe ) {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
protected abstract Object load(String entityName, Serializable identifier);
|
||||
|
||||
@Override
|
||||
|
@ -1161,6 +963,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
return scrollCustomQuery( getNativeQueryPlan( spec ).getCustomQuery(), queryParameters );
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExceptionConverter getExceptionConverter(){
|
||||
return exceptionConverter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void writeObject(ObjectOutputStream oos) throws IOException {
|
||||
log.trace( "Serializing " + getClass().getSimpleName() + " [" );
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.internal;
|
||||
|
||||
import javax.persistence.EntityExistsException;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.LockTimeoutException;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.PessimisticLockException;
|
||||
import javax.persistence.QueryTimeoutException;
|
||||
import javax.persistence.RollbackException;
|
||||
import java.io.Serializable;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.ObjectNotFoundException;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.StaleObjectStateException;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.UnresolvableObjectException;
|
||||
import org.hibernate.dialect.lock.LockingStrategyException;
|
||||
import org.hibernate.dialect.lock.OptimisticEntityLockException;
|
||||
import org.hibernate.dialect.lock.PessimisticEntityLockException;
|
||||
import org.hibernate.engine.spi.ExceptionConverter;
|
||||
import org.hibernate.loader.MultipleBagFetchException;
|
||||
|
||||
/**
|
||||
* @author Andrea Boriero
|
||||
*/
|
||||
public class ExceptionConverterImpl implements ExceptionConverter {
|
||||
private static final EntityManagerMessageLogger log = HEMLogging.messageLogger( ExceptionConverterImpl.class );
|
||||
|
||||
private final AbstractSharedSessionContract sharedSessionContract;
|
||||
|
||||
public ExceptionConverterImpl(AbstractSharedSessionContract sharedSessionContract) {
|
||||
this.sharedSessionContract = sharedSessionContract;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convertCommitException(RuntimeException e) {
|
||||
if ( sharedSessionContract.getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
Throwable wrappedException;
|
||||
if ( e instanceof PersistenceException ) {
|
||||
Throwable cause = e.getCause() == null ? e : e.getCause();
|
||||
if ( cause instanceof HibernateException ) {
|
||||
wrappedException = convert( (HibernateException) cause );
|
||||
}
|
||||
else {
|
||||
wrappedException = cause;
|
||||
}
|
||||
}
|
||||
else if ( e instanceof HibernateException ) {
|
||||
wrappedException = convert( (HibernateException) e );
|
||||
}
|
||||
else {
|
||||
wrappedException = e;
|
||||
}
|
||||
try {
|
||||
//as per the spec we should rollback if commit fails
|
||||
sharedSessionContract.getTransaction().rollback();
|
||||
}
|
||||
catch (Exception re) {
|
||||
//swallow
|
||||
}
|
||||
return new RollbackException( "Error while committing the transaction", wrappedException );
|
||||
}
|
||||
else {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e, LockOptions lockOptions) {
|
||||
Throwable cause = e;
|
||||
if ( cause instanceof StaleStateException ) {
|
||||
final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof LockingStrategyException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.PessimisticLockException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.QueryTimeoutException ) {
|
||||
final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof ObjectNotFoundException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueObjectException ) {
|
||||
final EntityExistsException converted = new EntityExistsException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueResultException ) {
|
||||
final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof UnresolvableObjectException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof QueryException ) {
|
||||
return new IllegalArgumentException( cause );
|
||||
}
|
||||
else if ( cause instanceof MultipleBagFetchException ) {
|
||||
return new IllegalArgumentException( cause );
|
||||
}
|
||||
else if ( cause instanceof TransientObjectException ) {
|
||||
try {
|
||||
sharedSessionContract.markForRollbackOnly();
|
||||
}
|
||||
catch (Exception ne) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnTransientObjectException( ne );
|
||||
}
|
||||
return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules
|
||||
}
|
||||
else {
|
||||
final PersistenceException converted = new PersistenceException( cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e) {
|
||||
return convert( e, null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(RuntimeException e) {
|
||||
RuntimeException result = e;
|
||||
if ( e instanceof HibernateException ) {
|
||||
result = convert( (HibernateException) e );
|
||||
}
|
||||
else {
|
||||
sharedSessionContract.markForRollbackOnly();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(RuntimeException e, LockOptions lockOptions) {
|
||||
RuntimeException result = e;
|
||||
if ( e instanceof HibernateException ) {
|
||||
result = convert( (HibernateException) e, lockOptions );
|
||||
}
|
||||
else {
|
||||
sharedSessionContract.markForRollbackOnly();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JDBCException convert(SQLException e, String message) {
|
||||
return sharedSessionContract.getJdbcServices().getSqlExceptionHelper().convert( e, message );
|
||||
}
|
||||
|
||||
protected PersistenceException wrapStaleStateException(StaleStateException e) {
|
||||
PersistenceException pe;
|
||||
if ( e instanceof StaleObjectStateException ) {
|
||||
final StaleObjectStateException sose = (StaleObjectStateException) e;
|
||||
final Serializable identifier = sose.getIdentifier();
|
||||
if ( identifier != null ) {
|
||||
try {
|
||||
final Object entity = sharedSessionContract.load( sose.getEntityName(), identifier );
|
||||
if ( entity instanceof Serializable ) {
|
||||
//avoid some user errors regarding boundary crossing
|
||||
pe = new OptimisticLockException( e.getMessage(), e, entity );
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
catch (EntityNotFoundException enfe) {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
protected PersistenceException wrapLockException(HibernateException e, LockOptions lockOptions) {
|
||||
final PersistenceException pe;
|
||||
if ( e instanceof OptimisticEntityLockException ) {
|
||||
final OptimisticEntityLockException lockException = (OptimisticEntityLockException) e;
|
||||
pe = new OptimisticLockException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else if ( e instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
pe = new LockTimeoutException( e.getMessage(), e, null );
|
||||
}
|
||||
else if ( e instanceof PessimisticEntityLockException ) {
|
||||
final PessimisticEntityLockException lockException = (PessimisticEntityLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException(
|
||||
lockException.getMessage(),
|
||||
lockException,
|
||||
lockException.getEntity()
|
||||
);
|
||||
}
|
||||
}
|
||||
else if ( e instanceof org.hibernate.PessimisticLockException ) {
|
||||
final org.hibernate.PessimisticLockException jdbcLockException = (org.hibernate.PessimisticLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
private void handlePersistenceException(PersistenceException e) {
|
||||
if ( e instanceof NoResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof NonUniqueResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof LockTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof QueryTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
sharedSessionContract.markForRollbackOnly();
|
||||
}
|
||||
catch (Exception ne) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnPersistenceException( ne );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -29,20 +29,13 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import javax.persistence.CacheRetrieveMode;
|
||||
import javax.persistence.CacheStoreMode;
|
||||
import javax.persistence.EntityExistsException;
|
||||
import javax.persistence.EntityGraph;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import javax.persistence.FlushModeType;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.LockTimeoutException;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.NonUniqueResultException;
|
||||
import javax.persistence.OptimisticLockException;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.PessimisticLockException;
|
||||
import javax.persistence.PessimisticLockScope;
|
||||
import javax.persistence.QueryTimeoutException;
|
||||
import javax.persistence.StoredProcedureQuery;
|
||||
import javax.persistence.TransactionRequiredException;
|
||||
import javax.persistence.Tuple;
|
||||
|
@ -75,8 +68,6 @@ import org.hibernate.SessionEventListener;
|
|||
import org.hibernate.SessionException;
|
||||
import org.hibernate.SharedSessionBuilder;
|
||||
import org.hibernate.SimpleNaturalIdLoadAccess;
|
||||
import org.hibernate.StaleObjectStateException;
|
||||
import org.hibernate.StaleStateException;
|
||||
import org.hibernate.TransientObjectException;
|
||||
import org.hibernate.TypeHelper;
|
||||
import org.hibernate.TypeMismatchException;
|
||||
|
@ -84,9 +75,6 @@ import org.hibernate.UnknownProfileException;
|
|||
import org.hibernate.UnresolvableObjectException;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.criterion.NaturalIdentifier;
|
||||
import org.hibernate.dialect.lock.LockingStrategyException;
|
||||
import org.hibernate.dialect.lock.OptimisticEntityLockException;
|
||||
import org.hibernate.dialect.lock.PessimisticEntityLockException;
|
||||
import org.hibernate.engine.internal.StatefulPersistenceContext;
|
||||
import org.hibernate.engine.jdbc.LobCreator;
|
||||
import org.hibernate.engine.jdbc.NonContextualLobCreator;
|
||||
|
@ -161,7 +149,6 @@ import org.hibernate.jpa.internal.util.FlushModeTypeHelper;
|
|||
import org.hibernate.jpa.internal.util.LockModeTypeHelper;
|
||||
import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer;
|
||||
import org.hibernate.jpa.spi.HibernateEntityManagerImplementor;
|
||||
import org.hibernate.loader.MultipleBagFetchException;
|
||||
import org.hibernate.loader.criteria.CriteriaLoader;
|
||||
import org.hibernate.loader.custom.CustomLoader;
|
||||
import org.hibernate.loader.custom.CustomQuery;
|
||||
|
@ -253,7 +240,6 @@ public final class SessionImpl
|
|||
|
||||
private transient LoadEvent loadEvent; //cached LoadEvent instance
|
||||
|
||||
|
||||
public SessionImpl(SessionFactoryImpl factory, SessionCreationOptions options) {
|
||||
super( factory, options );
|
||||
|
||||
|
@ -381,7 +367,7 @@ public final class SessionImpl
|
|||
internalClear();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,8 +440,7 @@ public final class SessionImpl
|
|||
return !isClosed();
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throwPersistenceException( he );
|
||||
return false;
|
||||
throw exceptionConverter.convert( he );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -764,17 +749,17 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
catch (MappingException e) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage() ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage() ) );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
checkNoUnresolvedActionsAfterOperation();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -789,10 +774,10 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
catch ( MappingException e ) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage() ) ) ;
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage() ) ) ;
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
finally {
|
||||
delayedAfterCompletion();
|
||||
|
@ -866,14 +851,14 @@ public final class SessionImpl
|
|||
checkNoUnresolvedActionsAfterOperation();
|
||||
}
|
||||
catch ( ObjectDeletedException sse ) {
|
||||
throw convert( new IllegalArgumentException( sse ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( sse ) );
|
||||
}
|
||||
catch ( MappingException e ) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
//including HibernateException
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
|
||||
return event.getResult();
|
||||
|
@ -889,14 +874,14 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
catch ( ObjectDeletedException sse ) {
|
||||
throw convert( new IllegalArgumentException( sse ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( sse ) );
|
||||
}
|
||||
catch ( MappingException e ) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
//including HibernateException
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
finally {
|
||||
delayedAfterCompletion();
|
||||
|
@ -1256,7 +1241,7 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
//including HibernateException
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
finally {
|
||||
delayedAfterCompletion();
|
||||
|
@ -1273,14 +1258,7 @@ public final class SessionImpl
|
|||
delayedAfterCompletion();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if ( !getSessionFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
if ( e instanceof HibernateException ) {
|
||||
handlePersistenceException( (HibernateException) e );
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
//including HibernateException
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
finally {
|
||||
delayedAfterCompletion();
|
||||
|
@ -1386,7 +1364,7 @@ public final class SessionImpl
|
|||
delayedAfterCompletion();
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1983,7 +1961,7 @@ public final class SessionImpl
|
|||
throw new IllegalArgumentException( e.getMessage(), e );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2037,7 +2015,7 @@ public final class SessionImpl
|
|||
throw new IllegalArgumentException( e.getMessage(), e );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3157,208 +3135,6 @@ public final class SessionImpl
|
|||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// HibernateEntityManagerImplementor impl
|
||||
|
||||
@Override
|
||||
public void handlePersistenceException(PersistenceException e) {
|
||||
if ( e instanceof NoResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof NonUniqueResultException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof LockTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
if ( e instanceof QueryTimeoutException ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
catch ( Exception ne ) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnPersistenceException( ne );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void throwPersistenceException(PersistenceException e) {
|
||||
throw convert( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e, LockOptions lockOptions) {
|
||||
Throwable cause = e;
|
||||
// if (e instanceof TransactionException){
|
||||
// cause = e.getCause();
|
||||
// }
|
||||
if ( cause instanceof StaleStateException ) {
|
||||
final PersistenceException converted = wrapStaleStateException( (StaleStateException) cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof LockingStrategyException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.PessimisticLockException ) {
|
||||
final PersistenceException converted = wrapLockException( (HibernateException) cause, lockOptions );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.QueryTimeoutException ) {
|
||||
final QueryTimeoutException converted = new QueryTimeoutException( cause.getMessage(), cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof ObjectNotFoundException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueObjectException ) {
|
||||
final EntityExistsException converted = new EntityExistsException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof org.hibernate.NonUniqueResultException ) {
|
||||
final NonUniqueResultException converted = new NonUniqueResultException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof UnresolvableObjectException ) {
|
||||
final EntityNotFoundException converted = new EntityNotFoundException( cause.getMessage() );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
else if ( cause instanceof QueryException ) {
|
||||
return new IllegalArgumentException( cause );
|
||||
}
|
||||
else if ( cause instanceof MultipleBagFetchException ) {
|
||||
return new IllegalArgumentException( cause );
|
||||
}
|
||||
else if ( cause instanceof TransientObjectException ) {
|
||||
try {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
catch ( Exception ne ) {
|
||||
//we do not want the subsequent exception to swallow the original one
|
||||
log.unableToMarkForRollbackOnTransientObjectException( ne );
|
||||
}
|
||||
return new IllegalStateException( e ); //Spec 3.2.3 Synchronization rules
|
||||
}
|
||||
else {
|
||||
final PersistenceException converted = new PersistenceException( cause );
|
||||
handlePersistenceException( converted );
|
||||
return converted;
|
||||
}
|
||||
}
|
||||
|
||||
public PersistenceException wrapLockException(HibernateException e, LockOptions lockOptions) {
|
||||
final PersistenceException pe;
|
||||
if ( e instanceof OptimisticEntityLockException ) {
|
||||
final OptimisticEntityLockException lockException = (OptimisticEntityLockException) e;
|
||||
pe = new OptimisticLockException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else if ( e instanceof org.hibernate.exception.LockTimeoutException ) {
|
||||
pe = new LockTimeoutException( e.getMessage(), e, null );
|
||||
}
|
||||
else if ( e instanceof PessimisticEntityLockException ) {
|
||||
final PessimisticEntityLockException lockException = (PessimisticEntityLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException( lockException.getMessage(), lockException, lockException.getEntity() );
|
||||
}
|
||||
}
|
||||
else if ( e instanceof org.hibernate.PessimisticLockException ) {
|
||||
final org.hibernate.PessimisticLockException jdbcLockException = (org.hibernate.PessimisticLockException) e;
|
||||
if ( lockOptions != null && lockOptions.getTimeOut() > -1 ) {
|
||||
// assume lock timeout occurred if a timeout or NO WAIT was specified
|
||||
pe = new LockTimeoutException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
else {
|
||||
pe = new PessimisticLockException( jdbcLockException.getMessage(), jdbcLockException, null );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RuntimeException convert(HibernateException e) {
|
||||
return convert( e, null );
|
||||
}
|
||||
|
||||
public RuntimeException convert(RuntimeException e) {
|
||||
RuntimeException result = e;
|
||||
if ( e instanceof HibernateException ) {
|
||||
result = convert( (HibernateException) e );
|
||||
}
|
||||
else {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public RuntimeException convert(RuntimeException e, LockOptions lockOptions) {
|
||||
RuntimeException result = e;
|
||||
if ( e instanceof HibernateException ) {
|
||||
result = convert( (HibernateException) e , lockOptions );
|
||||
}
|
||||
else {
|
||||
markForRollbackOnly();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void throwPersistenceException(HibernateException e) {
|
||||
throw convert( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public PersistenceException wrapStaleStateException(StaleStateException e) {
|
||||
PersistenceException pe;
|
||||
if ( e instanceof StaleObjectStateException ) {
|
||||
final StaleObjectStateException sose = (StaleObjectStateException) e;
|
||||
final Serializable identifier = sose.getIdentifier();
|
||||
if ( identifier != null ) {
|
||||
try {
|
||||
final Object entity = load( sose.getEntityName(), identifier );
|
||||
if ( entity instanceof Serializable ) {
|
||||
//avoid some user errors regarding boundary crossing
|
||||
pe = new OptimisticLockException( e.getMessage(), e, entity );
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
catch ( EntityNotFoundException enfe ) {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
}
|
||||
else {
|
||||
pe = new OptimisticLockException( e.getMessage(), e );
|
||||
}
|
||||
return pe;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LockOptions getLockRequest(LockModeType lockModeType, Map<String, Object> properties) {
|
||||
|
@ -3444,7 +3220,7 @@ public final class SessionImpl
|
|||
return query;
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3460,11 +3236,11 @@ public final class SessionImpl
|
|||
delete( entity );
|
||||
}
|
||||
catch (MappingException e) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
//including HibernateException
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3527,10 +3303,10 @@ public final class SessionImpl
|
|||
throw new IllegalArgumentException( e.getMessage(), e );
|
||||
}
|
||||
catch ( MappingException | TypeMismatchException | ClassCastException e ) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e, lockOptions );
|
||||
throw exceptionConverter.convert( e, lockOptions );
|
||||
}
|
||||
finally {
|
||||
getLoadQueryInfluencers().setFetchGraph( null );
|
||||
|
@ -3578,10 +3354,10 @@ public final class SessionImpl
|
|||
return byId( entityClass ).getReference( (Serializable) primaryKey );
|
||||
}
|
||||
catch ( MappingException | TypeMismatchException | ClassCastException e ) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3604,7 +3380,7 @@ public final class SessionImpl
|
|||
buildLockRequest( lockOptions ).lock( entity );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e, lockOptions );
|
||||
throw exceptionConverter.convert( e, lockOptions );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3630,7 +3406,7 @@ public final class SessionImpl
|
|||
setCacheMode( refreshCacheMode );
|
||||
|
||||
if ( !contains( entity ) ) {
|
||||
throw convert ( new IllegalArgumentException( "Entity not managed" ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( "Entity not managed" ) );
|
||||
}
|
||||
|
||||
if ( lockModeType != null ) {
|
||||
|
@ -3646,10 +3422,10 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
catch (MappingException e) {
|
||||
throw convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( e.getMessage(), e ) );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e, lockOptions );
|
||||
throw exceptionConverter.convert( e, lockOptions );
|
||||
}
|
||||
finally {
|
||||
setCacheMode( previousCacheMode );
|
||||
|
@ -3663,7 +3439,7 @@ public final class SessionImpl
|
|||
evict( entity );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3676,7 +3452,7 @@ public final class SessionImpl
|
|||
}
|
||||
|
||||
if ( !contains( entity ) ) {
|
||||
throw convert( new IllegalArgumentException( "entity not in the persistence context" ) );
|
||||
throw exceptionConverter.convert( new IllegalArgumentException( "entity not in the persistence context" ) );
|
||||
}
|
||||
|
||||
return LockModeTypeHelper.getLockModeType( getCurrentLockMode( entity ) );
|
||||
|
@ -3719,7 +3495,7 @@ public final class SessionImpl
|
|||
return (QueryImplementor<T>) criteriaCompiler().compile( (CompilableCriteria) criteriaQuery );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3730,7 +3506,7 @@ public final class SessionImpl
|
|||
return criteriaCompiler().compile( (CompilableCriteria) criteriaUpdate );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3741,7 +3517,7 @@ public final class SessionImpl
|
|||
return criteriaCompiler().compile( (CompilableCriteria) criteriaDelete );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3813,7 +3589,7 @@ public final class SessionImpl
|
|||
return memento.makeProcedureCall( this );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3823,7 +3599,7 @@ public final class SessionImpl
|
|||
return createStoredProcedureCall( procedureName );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3833,7 +3609,7 @@ public final class SessionImpl
|
|||
return createStoredProcedureCall( procedureName, resultClasses );
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3849,7 +3625,7 @@ public final class SessionImpl
|
|||
}
|
||||
}
|
||||
catch ( RuntimeException e ) {
|
||||
throw convert( e );
|
||||
throw exceptionConverter.convert( e );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3874,7 +3650,7 @@ public final class SessionImpl
|
|||
throw new TransactionRequiredException( e.getMessage() );
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw convert( he );
|
||||
throw exceptionConverter.convert( he );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ public class ExceptionMapperLegacyJpaImpl implements ExceptionMapper {
|
|||
@Override
|
||||
public RuntimeException mapManagedFlushFailure(String message, RuntimeException failure, SessionImplementor session) {
|
||||
if ( HibernateException.class.isInstance( failure ) ) {
|
||||
throw session.convert( failure );
|
||||
throw session.getExceptionConverter().convert( failure );
|
||||
}
|
||||
if ( PersistenceException.class.isInstance( failure ) ) {
|
||||
throw failure;
|
||||
|
|
|
@ -59,57 +59,6 @@ public interface HibernateEntityManagerImplementor extends HibernateEntityManage
|
|||
*/
|
||||
void markForRollbackOnly();
|
||||
|
||||
/**
|
||||
* Handles marking for rollback and other such operations that need to occur depending on the type of
|
||||
* exception being handled.
|
||||
*
|
||||
* @param e The exception being handled.
|
||||
*/
|
||||
void handlePersistenceException(PersistenceException e);
|
||||
|
||||
/**
|
||||
* Delegates to {@link #handlePersistenceException} and then throws the given exception.
|
||||
*
|
||||
* @param e The exception being handled and finally thrown.
|
||||
*/
|
||||
void throwPersistenceException(PersistenceException e);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
* Any appropriate/needed calls to {@link #handlePersistenceException} are also made.
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
* @param lockOptions The lock options in effect at the time of exception (can be null)
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e, LockOptions lockOptions);
|
||||
|
||||
/**
|
||||
* Converts a Hibernate-specific exception into a JPA-specified exception; note that the JPA sepcification makes use
|
||||
* of exceptions outside its exception hierarchy, though they are all runtime exceptions.
|
||||
* <p/>
|
||||
* Any appropriate/needed calls to {@link #handlePersistenceException} are also made.
|
||||
*
|
||||
* @param e The Hibernate excepton.
|
||||
*
|
||||
* @return The JPA-specified exception
|
||||
*/
|
||||
RuntimeException convert(HibernateException e);
|
||||
|
||||
RuntimeException convert(RuntimeException e);
|
||||
|
||||
/**
|
||||
* Delegates to {@link #convert} and then throws the given exception.
|
||||
*
|
||||
* @param e The exception being handled and finally thrown.
|
||||
*/
|
||||
void throwPersistenceException(HibernateException e);
|
||||
|
||||
PersistenceException wrapStaleStateException(StaleStateException e);
|
||||
|
||||
/**
|
||||
* Convert from JPA 2 {@link javax.persistence.LockModeType} & properties into {@link org.hibernate.LockOptions}
|
||||
*
|
||||
|
|
|
@ -602,7 +602,7 @@ public class ProcedureCallImpl<R>
|
|||
registerParameter( (ParameterRegistrationImplementor) registerParameter( position, type, mode ) );
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
getProducer().markForRollbackOnly();
|
||||
|
@ -620,7 +620,7 @@ public class ProcedureCallImpl<R>
|
|||
registerParameter( (ParameterRegistrationImplementor) registerParameter( parameterName, type, mode ) );
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
getProducer().markForRollbackOnly();
|
||||
|
@ -642,7 +642,7 @@ public class ProcedureCallImpl<R>
|
|||
return false;
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
getProducer().markForRollbackOnly();
|
||||
|
@ -724,7 +724,7 @@ public class ProcedureCallImpl<R>
|
|||
return -1;
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
getProducer().markForRollbackOnly();
|
||||
|
@ -751,7 +751,7 @@ public class ProcedureCallImpl<R>
|
|||
return null;
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
getProducer().markForRollbackOnly();
|
||||
|
|
|
@ -38,6 +38,7 @@ import org.hibernate.ScrollableResults;
|
|||
import org.hibernate.TypeMismatchException;
|
||||
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
|
||||
import org.hibernate.engine.query.spi.HQLQueryPlan;
|
||||
import org.hibernate.engine.spi.ExceptionConverter;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.engine.spi.RowSelection;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
|
@ -1091,7 +1092,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
throw new IllegalArgumentException( e );
|
||||
}
|
||||
catch (HibernateException he) {
|
||||
throw getProducer().convert( he );
|
||||
throw getExceptionConverter().convert( he );
|
||||
}
|
||||
finally {
|
||||
afterQuery();
|
||||
|
@ -1147,7 +1148,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
}
|
||||
catch ( HibernateException e) {
|
||||
if ( getProducer().getFactory().getSessionFactoryOptions().isJpaBootstrap() ) {
|
||||
throw getProducer().convert( e );
|
||||
throw getExceptionConverter().convert( e );
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
|
@ -1209,4 +1210,8 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
.getHQLQueryPlan( getQueryString(), false, Collections.<String, Filter>emptyMap() )
|
||||
.isSelect();
|
||||
}
|
||||
|
||||
protected ExceptionConverter getExceptionConverter(){
|
||||
return producer.getExceptionConverter();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.RollbackException;
|
||||
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
|
@ -48,8 +49,7 @@ public class TransactionCommitFailureTest {
|
|||
transactionFailureTrigger.set( true );
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
assertEquals(PersistenceException.class, e.getCause().getClass());
|
||||
catch (RollbackException e) {
|
||||
assertEquals( COMMIT_FAILURE, e.getCause().getMessage() );
|
||||
}
|
||||
finally {
|
||||
|
|
Loading…
Reference in New Issue