/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2010, Red Hat, Inc. and/or its affiliates or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat, Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.search.backend.impl;
import java.io.Serializable;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.TransactionManager;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.search.event.impl.FullTextIndexEventListener;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.HibernateException;
import org.hibernate.action.spi.AfterTransactionCompletionProcess;
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
import org.hibernate.engine.spi.ActionQueue;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.event.spi.FlushEventListener;
import org.hibernate.search.SearchException;
import org.hibernate.search.backend.TransactionContext;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import org.hibernate.service.Service;
import org.hibernate.service.jta.platform.spi.JtaPlatform;
/**
* Implementation of the transactional context on top of an EventSource (Session)
*
* @author Navin Surtani - navin@surtani.org
* @author Emmanuel Bernard
* @author Sanne Grinovero
*/
public class EventSourceTransactionContext implements TransactionContext, Serializable {
private static final Log log = LoggerFactory.make();
private final EventSource eventSource;
//this transient is required to break recursive serialization
private transient FullTextIndexEventListener flushListener;
//constructor time is too early to define the value of realTxInProgress,
//postpone it, otherwise doing
// " openSession - beginTransaction "
//will behave as "out of transaction" in the whole session lifespan.
private boolean realTxInProgress = false;
private boolean realTxInProgressInitialized = false;
public EventSourceTransactionContext(EventSource eventSource) {
this.eventSource = eventSource;
this.flushListener = getIndexWorkFlushEventListener();
}
public Object getTransactionIdentifier() {
if ( isRealTransactionInProgress() ) {
return eventSource.getTransaction();
}
else {
return eventSource;
}
}
public void registerSynchronization(Synchronization synchronization) {
if ( isRealTransactionInProgress() ) {
//use {Before|After}TransactionCompletionProcess instead of registerTransaction because it does not
//swallow transactions.
/*
* HSEARCH-540: the pre process must be both a BeforeTransactionCompletionProcess and a TX Synchronization.
*
* In a resource-local tx env, the beforeCommit phase is called after the flush, and prepares work queue.
* Also, any exceptions that occur during that are propagated (if a Synchronization was used, the exceptions
* would be eaten).
*
* In a JTA env, the before transaction completion is called before the flush, so not all changes are yet
* written. However, Synchronization-s do propagate exceptions, so they can be safely used.
*/
final ActionQueue actionQueue = eventSource.getActionQueue();
boolean isLocal = isLocalTransaction();
if ( isLocal ) {
//if local tx never use Synchronization
actionQueue.registerProcess( new DelegateToSynchronizationOnBeforeTx( synchronization ) );
}
else {
//TODO could we remove the action queue registration in this case?
actionQueue.registerProcess( new DelegateToSynchronizationOnBeforeTx( synchronization ) );
eventSource.getTransaction().registerSynchronization(
new BeforeCommitSynchronizationDelegator( synchronization )
);
}
//executed in all environments
actionQueue.registerProcess( new DelegateToSynchronizationOnAfterTx( synchronization ) );
}
else {
//registerSynchronization is only called if isRealTransactionInProgress or if
// a flushListener was found; still we might need to find the listener again
// as it might have been cleared by serialization (is transient).
flushListener = getIndexWorkFlushEventListener();
if ( flushListener != null ) {
flushListener.addSynchronization( eventSource, synchronization );
}
else {
//shouldn't happen if the code about serialization is fine:
throw new SearchException( "AssertionFailure: flushListener not registered any more.");
}
}
}
private boolean isLocalTransaction() {
//TODO make it better but I don't know how we can optimize it.
final TransactionManager transactionManager = getService(JtaPlatform.class).retrieveTransactionManager();
return transactionManager == null;
}
private <T extends Service> T getService(Class<T> serviceClass) {
return eventSource.getFactory().getServiceRegistry().getService(serviceClass);
}
private FullTextIndexEventListener getIndexWorkFlushEventListener() {
if ( this.flushListener != null) {
//for the "transient" case: might have been nullified.
return flushListener;
}
final Iterable<FlushEventListener> listeners = getService(EventListenerRegistry.class)
.getEventListenerGroup(EventType.FLUSH).listeners();
for ( FlushEventListener listener : listeners ) {
if ( FullTextIndexEventListener.class.isAssignableFrom( listener.getClass() ) ) {
return (FullTextIndexEventListener) listener;
}
}
log.debug( "FullTextIndexEventListener was not registered as FlushEventListener" );
return null;
}
//The code is not really fitting the method name;
//(unless you consider a flush as a mini-transaction)
//This is because we want to behave as "inTransaction" if the flushListener is registered.
public boolean isTransactionInProgress() {
// either it is a real transaction, or if we are capable to manage this in the IndexWorkFlushEventListener
return getIndexWorkFlushEventListener() != null || isRealTransactionInProgress();
}
private boolean isRealTransactionInProgress() {
if ( ! realTxInProgressInitialized ) {
realTxInProgress = eventSource.isTransactionInProgress();
realTxInProgressInitialized = true;
}
return realTxInProgress;
}
private static class DelegateToSynchronizationOnBeforeTx implements BeforeTransactionCompletionProcess {
private final Synchronization synchronization;
DelegateToSynchronizationOnBeforeTx(Synchronization synchronization) {
this.synchronization = synchronization;
}
public void doBeforeTransactionCompletion(SessionImplementor sessionImplementor) {
try {
synchronization.beforeCompletion();
}
catch ( Exception e ) {
throw new HibernateException( "Error while indexing in Hibernate Search (before transaction completion)", e);
}
}
}
private static class DelegateToSynchronizationOnAfterTx implements AfterTransactionCompletionProcess {
private final Synchronization synchronization;
DelegateToSynchronizationOnAfterTx(Synchronization synchronization) {
this.synchronization = synchronization;
}
public void doAfterTransactionCompletion(boolean success, SessionImplementor sessionImplementor) {
try {
synchronization.afterCompletion( success ? Status.STATUS_COMMITTED : Status.STATUS_ROLLEDBACK );
}
catch ( Exception e ) {
throw new HibernateException( "Error while indexing in Hibernate Search (ater transaction completion)", e);
}
}
}
private static class BeforeCommitSynchronizationDelegator implements Synchronization {
private final Synchronization synchronization;
public BeforeCommitSynchronizationDelegator(Synchronization sync) {
this.synchronization = sync;
}
public void beforeCompletion() {
this.synchronization.beforeCompletion();
}
public void afterCompletion(int status) {
//do not delegate
}
}
}