/*
* Copyright 2004-2009 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.compass.gps.device.hibernate;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.CompassException;
import org.compass.core.CompassSession;
import org.compass.core.spi.InternalCompassSession;
import org.compass.core.transaction.AbstractTransaction;
import org.compass.core.transaction.TransactionException;
import org.compass.core.transaction.TransactionFactory;
import org.hibernate.FlushMode;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.engine.SessionImplementor;
/**
* @author kimchy
*/
public class HibernateSyncTransaction extends AbstractTransaction {
private static final Log log = LogFactory.getLog(HibernateSyncTransaction.class);
private SessionFactory sessionFactory;
/**
* Did we start the Hibernate transaction
*/
private boolean newTransaction;
/**
* Is this the up most level controlling the Compass transaction
*/
private boolean controllingNewTransaction = false;
private InternalCompassSession session;
private boolean commitFailed;
private boolean commitBeforeCompletion;
private Transaction transaction;
public HibernateSyncTransaction(SessionFactory sessionFactory, boolean commitBeforeCompletion, TransactionFactory transactionFactory) {
super(transactionFactory);
this.sessionFactory = sessionFactory;
this.commitBeforeCompletion = commitBeforeCompletion;
}
public void begin(InternalCompassSession session) throws CompassException {
this.session = session;
try {
controllingNewTransaction = true;
SessionImplementor hibernateSession = ((SessionImplementor) sessionFactory.getCurrentSession());
newTransaction = !hibernateSession.isTransactionInProgress();
transaction = sessionFactory.getCurrentSession().getTransaction();
if (newTransaction) {
if (log.isDebugEnabled()) {
log.debug("Beginning new Hibernate transaction, and a new compass transaction on thread ["
+ Thread.currentThread().getName() + "]");
}
session.getSearchEngine().begin();
if (session.isReadOnly()) {
hibernateSession.setFlushMode(FlushMode.MANUAL);
}
transaction.begin();
} else {
// joining an exisiting transaction
session.getSearchEngine().begin();
if (log.isDebugEnabled()) {
log.debug("Joining an existing Hibernate transaction, starting a new compass transaction on thread ["
+ Thread.currentThread().getName() + "]");
}
}
transaction.registerSynchronization(new HibernateTransactionSynchronization(session, transaction, newTransaction, commitBeforeCompletion, transactionFactory));
} catch (Exception e) {
throw new TransactionException("Begin failed with exception", e);
}
setBegun(true);
}
/**
* Called by the factory when joining an already running compass transaction
*/
public void join(InternalCompassSession session) throws CompassException {
this.session = session;
controllingNewTransaction = false;
if (log.isDebugEnabled()) {
log.debug("Joining an existing compass transcation on thread [" + Thread.currentThread().getName() + "]");
}
}
protected void doCommit() throws CompassException {
if (!controllingNewTransaction) {
if (log.isDebugEnabled()) {
log.debug("Not committing Hibernate transaction since compass does not control it on thread ["
+ Thread.currentThread().getName() + "]");
}
return;
}
if (newTransaction) {
if (log.isDebugEnabled()) {
log.debug("Committing Hibernate transaction controlled by compass on thread ["
+ Thread.currentThread().getName() + "]");
}
try {
transaction.commit();
} catch (Exception e) {
commitFailed = true;
// so the transaction is already rolled back, by Hibernate spec
throw new TransactionException("Commit failed", e);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Commit called, let Hibernate synchronization commit the transaciton on thread ["
+ Thread.currentThread().getName() + "]");
}
}
}
protected void doRollback() throws CompassException {
try {
if (newTransaction) {
if (log.isDebugEnabled()) {
log.debug("Rolling back Hibernate transaction controlled by compass on thread [" + Thread.currentThread().getName() + "]");
}
if (!commitFailed)
transaction.rollback();
} else {
if (log.isDebugEnabled()) {
log.debug("Marking Hibernate transaction as rolled back since compass controlls it on thread [" +
Thread.currentThread().getName() + "]");
}
// no way to mark a transaction as rolled back, assume throwing the exception will make it
// transaction.setRollbackOnly();
}
} catch (Exception e) {
throw new TransactionException("Rollback failed with exception", e);
}
}
public boolean wasRolledBack() throws TransactionException {
if (!isBegun())
return false;
if (commitFailed)
return true;
return transaction.wasRolledBack();
}
public boolean wasCommitted() throws TransactionException {
if (!isBegun() || commitFailed)
return false;
return transaction.wasCommitted();
}
public CompassSession getSession() {
return this.session;
}
private static class HibernateTransactionSynchronization implements Synchronization {
private static final Log log = LogFactory.getLog(HibernateTransactionSynchronization.class);
private InternalCompassSession session;
private Transaction tx;
private boolean compassControlledHibernateTransaction;
private boolean commitBeforeCompletion;
private TransactionFactory transactionFactory;
public HibernateTransactionSynchronization(InternalCompassSession session, Transaction tx,
boolean compassControlledHibernateTransaction, boolean commitBeforeCompletion,
TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
this.session = session;
this.tx = tx;
this.compassControlledHibernateTransaction = compassControlledHibernateTransaction;
this.commitBeforeCompletion = commitBeforeCompletion;
}
public void beforeCompletion() {
if (!commitBeforeCompletion) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Committing compass transaction using Hibernate synchronization beforeCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().commit(true);
}
public void afterCompletion(int status) {
try {
if (!commitBeforeCompletion) {
if (status == Status.STATUS_COMMITTED) {
if (log.isDebugEnabled()) {
log.debug("Committing compass transaction using Hibernate synchronization afterCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().commit(true);
} else {
if (log.isDebugEnabled()) {
log.debug("Rolling back compass transaction using Hibernate synchronization afterCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().rollback();
}
}
} catch (Exception e) {
// TODO swallow??????
log.error("Exception occured when sync with transaction", e);
} finally {
session.evictAll();
((HibernateSyncTransactionFactory) transactionFactory).unbindSessionFromTransaction(tx, session);
// close the session AFTER we cleared it from the transaction,
// so it will be actually closed (and only if we are not
// controlling the trnasction)
if (!compassControlledHibernateTransaction) {
session.close();
}
}
}
}
}