/* * 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.ArrayList; import java.util.List; import org.hibernate.Criteria; import org.hibernate.ScrollMode; import org.hibernate.ScrollableResults; import org.hibernate.SessionFactory; import org.hibernate.StatelessSession; import org.hibernate.Transaction; import org.hibernate.criterion.Projections; import org.hibernate.search.batchindexing.MassIndexerProgressMonitor; import org.hibernate.search.exception.ErrorHandler; import org.hibernate.search.util.logging.impl.LoggerFactory; import org.hibernate.search.util.logging.impl.Log; /** * This Runnable is going to feed the indexing queue * with the identifiers of all the entities going to be indexed. * This step in the indexing process is not parallel (should be * done by one thread per type) so that a single transaction is used * to define the group of entities to be indexed. * Produced identifiers are put in the destination queue grouped in List * instances: the reason for this is to load them in batches * in the next step and reduce contention on the queue. * * @author Sanne Grinovero */ public class IdentifierProducer implements StatelessSessionAwareRunnable { private static final Log log = LoggerFactory.make(); private final ProducerConsumerQueue<List<Serializable>> destination; private final SessionFactory sessionFactory; private final int batchSize; private final Class<?> indexedType; private final MassIndexerProgressMonitor monitor; private final long objectsLimit; private final ErrorHandler errorHandler; private final int idFetchSize; /** * @param fromIdentifierListToEntities the target queue where the produced identifiers are sent to * @param sessionFactory the Hibernate SessionFactory to use to load entities * @param objectLoadingBatchSize affects mostly the next consumer: IdentifierConsumerEntityProducer * @param indexedType the entity type to be loaded * @param monitor to monitor indexing progress * @param objectsLimit if not zero * @param errorHandler how to handle unexpected errors */ public IdentifierProducer( ProducerConsumerQueue<List<Serializable>> fromIdentifierListToEntities, SessionFactory sessionFactory, int objectLoadingBatchSize, Class<?> indexedType, MassIndexerProgressMonitor monitor, long objectsLimit, ErrorHandler errorHandler, int idFetchSize) { this.destination = fromIdentifierListToEntities; this.sessionFactory = sessionFactory; this.batchSize = objectLoadingBatchSize; this.indexedType = indexedType; this.monitor = monitor; this.objectsLimit = objectsLimit; this.errorHandler = errorHandler; this.idFetchSize = idFetchSize; log.trace( "created" ); } public void run(StatelessSession upperSession) { log.trace( "started" ); try { inTransactionWrapper(upperSession); } catch (Throwable e) { errorHandler.handleException( log.massIndexerUnexpectedErrorMessage() , e ); } finally{ destination.producerStopping(); } log.trace( "finished" ); } private void inTransactionWrapper(StatelessSession upperSession) throws Exception { StatelessSession session = upperSession; if (upperSession == null) { session = sessionFactory.openStatelessSession(); } try { Transaction transaction = Helper.getTransactionAndMarkForJoin( session ); transaction.begin(); loadAllIdentifiers( session ); transaction.commit(); } catch (InterruptedException e) { // just quit Thread.currentThread().interrupt(); } finally { if (upperSession == null) { session.close(); } } } private void loadAllIdentifiers(final StatelessSession session) throws InterruptedException { Number countAsNumber = (Number) session .createCriteria( indexedType ) .setProjection( Projections.rowCount() ) .setCacheable( false ) .uniqueResult(); long totalCount = countAsNumber.longValue(); if ( objectsLimit != 0 && objectsLimit < totalCount ) { totalCount = objectsLimit; } if ( log.isDebugEnabled() ) log.debugf( "going to fetch %d primary keys", totalCount); monitor.addToTotalCount( totalCount ); Criteria criteria = session .createCriteria( indexedType ) .setProjection( Projections.id() ) .setCacheable( false ) .setFetchSize( idFetchSize ); ScrollableResults results = criteria.scroll( ScrollMode.FORWARD_ONLY ); ArrayList<Serializable> destinationList = new ArrayList<Serializable>( batchSize ); long counter = 0; try { while ( results.next() ) { Serializable id = (Serializable) results.get( 0 ); destinationList.add( id ); if ( destinationList.size() == batchSize ) { enqueueList( destinationList ); destinationList = new ArrayList<Serializable>( batchSize ); } counter++; if ( counter == totalCount ) { break; } } } finally { results.close(); } enqueueList( destinationList ); } private void enqueueList(final List<Serializable> idsList) throws InterruptedException { if ( ! idsList.isEmpty() ) { destination.put( idsList ); log.tracef( "produced a list of ids %s", idsList ); } } }