/*
* 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.util.Properties;
import javax.transaction.Synchronization;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.search.backend.spi.Work;
import org.hibernate.search.backend.spi.WorkType;
import org.hibernate.search.backend.spi.Worker;
import org.hibernate.search.engine.spi.EntityIndexBinder;
import org.hibernate.search.engine.spi.SearchFactoryImplementor;
import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor;
import org.hibernate.search.indexes.interceptor.IndexingOverride;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.SearchException;
import org.hibernate.search.backend.TransactionContext;
import org.hibernate.search.spi.InstanceInitializer;
import org.hibernate.search.spi.WorkerBuildContext;
import org.hibernate.search.util.impl.WeakIdentityHashMap;
import org.hibernate.search.util.logging.impl.LoggerFactory;
/**
* Queue works per transaction.
* If out of transaction, the work is executed right away
* <p/>
* When <code>hibernate.search.worker.type</code> is set to <code>async</code>
* the work is done in a separate thread (threads are pooled)
*
* @author Emmanuel Bernard
*/
public class TransactionalWorker implements Worker {
//note: there is one Worker instance per SearchFactory, reused concurrently for all sessions.
private static final Log log = LoggerFactory.make();
//this is being used from different threads, but doesn't need a
//synchronized map since for a given transaction, we have not concurrent access
protected final WeakIdentityHashMap<Object, Synchronization> synchronizationPerTransaction = new WeakIdentityHashMap<Object, Synchronization>();
private QueueingProcessor queueingProcessor;
private SearchFactoryImplementor factory;
private InstanceInitializer instanceInitializer;
private boolean transactionExpected;
public void performWork(Work<?> work, TransactionContext transactionContext) {
final Class<?> entityType = instanceInitializer.getClassFromWork( work );
EntityIndexBinder indexBindingForEntity = factory.getIndexBindingForEntity( entityType );
if ( indexBindingForEntity == null
&& factory.getDocumentBuilderContainedEntity( entityType ) == null ) {
throw new SearchException( "Unable to perform work. Entity Class is not @Indexed nor hosts @ContainedIn: " + entityType );
}
work = interceptWork(indexBindingForEntity, work);
if (work == null) {
//nothing to do
return;
}
if ( transactionContext.isTransactionInProgress() ) {
Object transactionIdentifier = transactionContext.getTransactionIdentifier();
PostTransactionWorkQueueSynchronization txSync = (PostTransactionWorkQueueSynchronization)
synchronizationPerTransaction.get( transactionIdentifier );
if ( txSync == null || txSync.isConsumed() ) {
txSync = new PostTransactionWorkQueueSynchronization(
queueingProcessor, synchronizationPerTransaction, factory
);
transactionContext.registerSynchronization( txSync );
synchronizationPerTransaction.put( transactionIdentifier, txSync );
}
txSync.add( work );
}
else {
if ( transactionExpected ) {
// this is a workaround: isTransactionInProgress should return "true"
// for correct configurations.
log.pushedChangesOutOfTransaction();
}
WorkQueue queue = new WorkQueue( factory );
queueingProcessor.add( work, queue );
queueingProcessor.prepareWorks( queue );
queueingProcessor.performWorks( queue );
}
}
private <T> Work<T> interceptWork(EntityIndexBinder indexBindingForEntity, Work<T> work) {
if (indexBindingForEntity == null) {
return work;
}
EntityIndexingInterceptor<? super T> interceptor = (EntityIndexingInterceptor<? super T> ) indexBindingForEntity.getEntityIndexingInterceptor();
if (interceptor == null) {
return work;
}
IndexingOverride operation;
switch ( work.getType() ) {
case ADD:
operation = interceptor.onAdd( work.getEntity() );
break;
case UPDATE:
operation = interceptor.onUpdate( work.getEntity() );
break;
case DELETE:
operation = interceptor.onDelete( work.getEntity() );
break;
case COLLECTION:
operation = interceptor.onCollectionUpdate( work.getEntity() );
break;
case PURGE:
case PURGE_ALL:
case INDEX:
operation = IndexingOverride.APPLY_DEFAULT;
break;
default:
throw new AssertionFailure( "Unknown work type: " + work.getType() );
}
Work<T> result = work;
Class<T> entityClass = work.getEntityClass();
switch ( operation ) {
case APPLY_DEFAULT:
break;
case SKIP:
result = null;
log.forceSkipIndexOperationViaInterception( entityClass, work.getType() );
break;
case UPDATE:
result = new Work<T>( work.getEntity(), work.getId(), WorkType.UPDATE );
log.forceUpdateOnIndexOperationViaInterception( entityClass, work.getType() );
break;
case REMOVE:
//This works because other Work constructors are never used from WorkType ADD, UPDATE, REMOVE, COLLECTION
//TODO should we force isIdentifierRollback to false if the operation is not a delete?
result = new Work<T>( work.getEntity(), work.getId(), WorkType.DELETE, work.isIdentifierWasRolledBack() );
log.forceRemoveOnIndexOperationViaInterception( entityClass, work.getType() );
break;
default:
throw new AssertionFailure( "Unknown action type: " + operation );
}
return result;
}
public void initialize(Properties props, WorkerBuildContext context, QueueingProcessor queueingProcessor) {
this.queueingProcessor = queueingProcessor;
this.factory = context.getUninitializedSearchFactory();
this.transactionExpected = context.isTransactionManagerExpected();
this.instanceInitializer = context.getInstanceInitializer();
}
public void close() {
}
public void flushWorks(TransactionContext transactionContext) {
if ( transactionContext.isTransactionInProgress() ) {
Object transaction = transactionContext.getTransactionIdentifier();
PostTransactionWorkQueueSynchronization txSync = (PostTransactionWorkQueueSynchronization)
synchronizationPerTransaction.get( transaction );
if ( txSync != null && !txSync.isConsumed() ) {
txSync.flushWorks();
}
}
}
}