/* * 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.batchindexing.impl; import java.io.Serializable; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.search.backend.AddLuceneWork; import org.hibernate.search.backend.impl.batch.BatchBackend; import org.hibernate.search.batchindexing.MassIndexerProgressMonitor; import org.hibernate.search.bridge.TwoWayFieldBridge; import org.hibernate.search.bridge.spi.ConversionContext; import org.hibernate.search.bridge.util.impl.ContextualExceptionBridgeHelper; import org.hibernate.search.engine.impl.HibernateSessionLoadingInitializer; import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity; import org.hibernate.search.engine.spi.EntityIndexBinder; import org.hibernate.search.engine.spi.SearchFactoryImplementor; import org.hibernate.search.exception.ErrorHandler; import org.hibernate.search.indexes.interceptor.EntityIndexingInterceptor; import org.hibernate.search.indexes.interceptor.IndexingOverride; import org.hibernate.search.spi.InstanceInitializer; import org.hibernate.search.util.impl.HibernateHelper; import org.hibernate.search.util.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; /** * Component of batch-indexing pipeline, using chained producer-consumers. * This Runnable will consume entities taken one-by-one from the queue * and produce for each entity an AddLuceneWork to the output queue. * * @author Sanne Grinovero */ public class EntityConsumerLuceneWorkProducer implements SessionAwareRunnable { private static final Log log = LoggerFactory.make(); private final ProducerConsumerQueue<List<?>> source; private final SessionFactory sessionFactory; private final Map<Class<?>, EntityIndexBinder> entityIndexBinders; private final MassIndexerProgressMonitor monitor; private final CacheMode cacheMode; private final CountDownLatch producerEndSignal; private final BatchBackend backend; private final ErrorHandler errorHandler; public EntityConsumerLuceneWorkProducer( ProducerConsumerQueue<List<?>> entitySource, MassIndexerProgressMonitor monitor, SessionFactory sessionFactory, CountDownLatch producerEndSignal, SearchFactoryImplementor searchFactory, CacheMode cacheMode, BatchBackend backend, ErrorHandler errorHandler) { this.source = entitySource; this.monitor = monitor; this.sessionFactory = sessionFactory; this.producerEndSignal = producerEndSignal; this.cacheMode = cacheMode; this.backend = backend; this.errorHandler = errorHandler; this.entityIndexBinders = searchFactory.getIndexBindingForEntity(); } public void run(Session upperSession) { Session session = upperSession; if ( upperSession == null ) { session = sessionFactory.openSession(); } session.setFlushMode( FlushMode.MANUAL ); session.setCacheMode( cacheMode ); session.setDefaultReadOnly( true ); try { Transaction transaction = Helper.getTransactionAndMarkForJoin( session ); transaction.begin(); indexAllQueue( session ); transaction.commit(); } catch (Throwable e) { errorHandler.handleException( log.massIndexerUnexpectedErrorMessage() , e ); } finally { producerEndSignal.countDown(); if ( upperSession == null ) { session.close(); } } log.debug( "finished" ); } private void indexAllQueue(Session session) { final InstanceInitializer sessionInitializer = new HibernateSessionLoadingInitializer( (SessionImplementor) session ); try { ConversionContext contextualBridge = new ContextualExceptionBridgeHelper(); while ( true ) { List<?> takeList = source.take(); if ( takeList == null ) { break; } else { log.tracef( "received a list of objects to index: %s", takeList ); for ( Object take : takeList ) { //trick to attach the objects to session: session.buildLockRequest( LockOptions.NONE ).lock( take ); index( take, session, sessionInitializer, contextualBridge ); monitor.documentsBuilt( 1 ); session.clear(); } } } } catch (InterruptedException e) { // just quit Thread.currentThread().interrupt(); } } @SuppressWarnings("unchecked") private void index(Object entity, Session session, InstanceInitializer sessionInitializer, ConversionContext conversionContext) throws InterruptedException { Serializable id = session.getIdentifier( entity ); Class<?> clazz = HibernateHelper.getClass( entity ); EntityIndexBinder entityIndexBinding = entityIndexBinders.get( clazz ); if ( entityIndexBinding == null ) { // it might be possible to receive not-indexes subclasses of the currently indexed type; // being not-indexed, we skip them. // FIXME for improved performance: avoid loading them in an early phase. return; } EntityIndexingInterceptor interceptor = entityIndexBinding.getEntityIndexingInterceptor(); if ( interceptor != null ) { IndexingOverride onAdd = interceptor.onAdd( entity ); switch ( onAdd ) { case REMOVE: case SKIP: return; } //default: continue indexing this instance } DocumentBuilderIndexedEntity docBuilder = entityIndexBinding.getDocumentBuilder(); TwoWayFieldBridge idBridge = docBuilder.getIdBridge(); conversionContext.pushProperty( docBuilder.getIdKeywordName() ); String idInString = null; try { idInString = conversionContext .setClass( clazz ) .twoWayConversionContext( idBridge ) .objectToString( id ); } finally { conversionContext.popProperty(); } //depending on the complexity of the object graph going to be indexed it's possible //that we hit the database several times during work construction. AddLuceneWork addWork = docBuilder.createAddWork( clazz, entity, id, idInString, sessionInitializer, conversionContext ); backend.enqueueAsyncWork( addWork ); } }