/*
* 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.spring.transaction;
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.config.CompassEnvironment;
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.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
public class SpringSyncTransaction extends AbstractTransaction {
private static final Log log = LogFactory.getLog(SpringSyncTransaction.class);
private TransactionStatus status;
/**
* This flag means if this transaction controls the COMPASS transaction (i.e. it is the top level compass
* transaction)
*/
private boolean controllingNewTransaction = false;
private boolean commitFailed;
private PlatformTransactionManager transactionManager;
private InternalCompassSession session;
public SpringSyncTransaction(TransactionFactory transactionFactory) {
super(transactionFactory);
}
public void begin(PlatformTransactionManager transactionManager, InternalCompassSession session, boolean commitBeforeCompletion) {
this.session = session;
this.transactionManager = transactionManager;
// the factory called begin, so we are in charge, if we were not, than
// it would not call begin (we are in charge of the COMPASS transaction,
// the spring one is handled later)
controllingNewTransaction = true;
if (transactionManager != null) {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
int timeout = session.getSettings().getSettingAsInt(CompassEnvironment.Transaction.TRANSACTION_TIMEOUT, -1);
if (timeout != -1) {
transactionDefinition.setTimeout(timeout);
}
transactionDefinition.setReadOnly(session.isReadOnly());
status = transactionManager.getTransaction(transactionDefinition);
}
session.getSearchEngine().begin();
SpringTransactionSynchronization sync;
if (transactionManager != null) {
if (log.isDebugEnabled()) {
if (status.isNewTransaction()) {
log.debug("Beginning new Spring transaction, and a new compass transaction on thread ["
+ Thread.currentThread().getName() + "]");
} else {
log.debug("Joining Spring transaction, and starting a new compass transaction on thread ["
+ Thread.currentThread().getName() + "]");
}
}
sync = new SpringTransactionSynchronization(session, status.isNewTransaction(), commitBeforeCompletion, transactionFactory);
} else {
if (log.isDebugEnabled()) {
log.debug("Joining Spring transaction, and starting a new compass transaction on thread ["
+ Thread.currentThread().getName() + "]");
}
sync = new SpringTransactionSynchronization(session, false, commitBeforeCompletion, transactionFactory);
}
TransactionSynchronizationManager.registerSynchronization(sync);
setBegun(true);
}
/**
* Called by factory when already in a 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 transaction since compass does not control it on thread ["
+ Thread.currentThread().getName() + "]");
}
return;
}
if (transactionManager == null) {
// do nothing, it could only get here if the spring transaction was
// started and we synch on it
return;
}
if (status.isNewTransaction()) {
if (log.isDebugEnabled()) {
log.debug("Committing Spring transaction controlled by compass on thread ["
+ Thread.currentThread().getName() + "]");
}
try {
transactionManager.commit(status);
} catch (Exception e) {
commitFailed = true;
// so the transaction is already rolled back, by JTA spec
throw new TransactionException("Commit failed", e);
}
} else {
if (log.isDebugEnabled()) {
log.debug("Commit called, let Spring synchronization commit the transaciton on thread ["
+ Thread.currentThread().getName() + "]");
}
}
}
protected void doRollback() throws CompassException {
if (transactionManager == null) {
// do nothing, it could only get here if the spring transaction was
// started and we synch on it
return;
}
try {
if (status.isNewTransaction()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back Spring transaction controlled by compass on thread ["
+ Thread.currentThread().getName() + "]");
}
if (!commitFailed)
transactionManager.rollback(status);
} else {
if (log.isDebugEnabled()) {
log.debug("Marking Spring transaction as rolled back since compass controlls it on thread [" +
Thread.currentThread().getName() + "]");
}
status.setRollbackOnly();
}
} catch (Exception e) {
throw new TransactionException("Rollback failed with exception", e);
}
}
public boolean wasRolledBack() throws CompassException {
throw new TransactionException("Not supported");
}
public boolean wasCommitted() throws CompassException {
throw new TransactionException("Not supported");
}
public CompassSession getSession() {
return this.session;
}
public static class SpringTransactionSynchronization implements TransactionSynchronization {
private InternalCompassSession session;
private boolean compassControledTransaction;
private boolean commitBeforeCompletion;
private TransactionFactory transactionFactory;
public SpringTransactionSynchronization(InternalCompassSession session, boolean compassControledTransaction,
boolean commitBeforeCompletion, TransactionFactory transactionFactory) {
this.session = session;
this.compassControledTransaction = compassControledTransaction;
this.commitBeforeCompletion = commitBeforeCompletion;
this.transactionFactory = transactionFactory;
}
public InternalCompassSession getSession() {
return this.session;
}
public void suspend() {
}
public void resume() {
}
public void beforeCommit(boolean readOnly) {
}
public void beforeCompletion() {
if (!commitBeforeCompletion) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Committing compass transaction using Spring synchronization beforeCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().commit(true);
}
public void afterCommit() {
}
public void afterCompletion(int status) {
try {
if (status == STATUS_COMMITTED) {
if (!commitBeforeCompletion) {
if (log.isDebugEnabled()) {
log.debug("Committing compass transaction using Spring synchronization afterCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().commit(true);
}
} else {
// in case of STATUS_ROLLBACK or STATUS_UNKNOWN
if (log.isDebugEnabled()) {
log.debug("Rolling back compass transaction using Spring synchronization afterCompletion on thread [" +
Thread.currentThread().getName() + "]");
}
session.getSearchEngine().rollback();
}
} catch (Exception e) {
log.error("Exception occured when sync with transaction", e);
// TODO swallow??????
} finally {
((SpringSyncTransactionFactory) transactionFactory).unbindSessionFromTransaction(this, session);
session.evictAll();
// close the session AFTER we cleared it from the transaction,
// so it will be actually closed. Also close it only if we do
// not contoll the transaction
// (otherwise it will be closed by the calling template)
if (!compassControledTransaction) {
session.close();
}
}
}
}
}