/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.massindex.impl; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.List; import java.util.Map; import org.hibernate.CacheMode; import org.hibernate.FlushMode; import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.ogm.loader.impl.OgmLoadingContext; import org.hibernate.ogm.loader.impl.TupleBasedEntityLoader; import org.hibernate.ogm.model.spi.Tuple; import org.hibernate.ogm.persister.impl.OgmEntityPersister; import org.hibernate.search.backend.AddLuceneWork; import org.hibernate.search.backend.spi.BatchBackend; import org.hibernate.search.batchindexing.MassIndexerProgressMonitor; 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.integration.impl.ExtendedSearchIntegrator; import org.hibernate.search.engine.service.spi.ServiceManager; import org.hibernate.search.engine.spi.DocumentBuilderIndexedEntity; import org.hibernate.search.engine.spi.EntityIndexBinding; import org.hibernate.search.exception.ErrorHandler; import org.hibernate.search.hcore.util.impl.HibernateHelper; 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.logging.impl.Log; import org.hibernate.search.util.logging.impl.LoggerFactory; /** * Component of batch-indexing pipeline, using chained producer-consumers. * <p> * This Runnable will consume {@link Tuple} objects taken one-by-one and it will create an {@link AddLuceneWork} for the * corresponding entity. * * @author Sanne Grinovero * @author Davide D'Alto */ public class TupleIndexer implements SessionAwareRunnable { private static final Log log = LoggerFactory.make(); private final SessionFactoryImplementor sessionFactory; private final Map<Class<?>, EntityIndexBinding> entityIndexBindings; private final MassIndexerProgressMonitor monitor; private final CacheMode cacheMode; private final BatchBackend backend; private final ErrorHandler errorHandler; private final Class<?> indexedType; private final ServiceManager serviceManager; private final String tenantId; public TupleIndexer(Class<?> indexedType, MassIndexerProgressMonitor monitor, SessionFactoryImplementor sessionFactory, ExtendedSearchIntegrator searchIntegrator, CacheMode cacheMode, BatchBackend backend, ErrorHandler errorHandler, String tenantId) { this.indexedType = indexedType; this.monitor = monitor; this.sessionFactory = sessionFactory; this.cacheMode = cacheMode; this.backend = backend; this.errorHandler = errorHandler; this.tenantId = tenantId; //can be null when multi-tenancy isn't being used this.entityIndexBindings = searchIntegrator.getIndexBindings(); serviceManager = searchIntegrator.getServiceManager(); } private void index(Session session, Object entity) { try { final InstanceInitializer sessionInitializer = new HibernateSessionLoadingInitializer( (SessionImplementor) session ); final ConversionContext contextualBridge = new ContextualExceptionBridgeHelper(); // trick to attach the objects to session: session.buildLockRequest( LockOptions.NONE ).lock( entity ); index( entity, session, sessionInitializer, contextualBridge ); monitor.documentsBuilt( 1 ); session.clear(); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); } } private void index(Object entity, Session session, InstanceInitializer sessionInitializer, ConversionContext conversionContext) throws InterruptedException { Class<?> clazz = HibernateHelper.getClass( entity ); EntityIndexBinding entityIndexBinding = entityIndexBindings.get( clazz ); // 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. if ( entityIndexBinding != null ) { EntityIndexingInterceptor<?> interceptor = entityIndexBinding.getEntityIndexingInterceptor(); if ( isNotSkippable( interceptor, entity ) ) { Serializable id = session.getIdentifier( entity ); AddLuceneWork addWork = createAddLuceneWork( session.getTenantIdentifier(), entity, sessionInitializer, conversionContext, id, clazz, entityIndexBinding ); backend.enqueueAsyncWork( addWork ); } } } private AddLuceneWork createAddLuceneWork(String tenantIdentifier, Object entity, InstanceInitializer sessionInitializer, ConversionContext conversionContext, Serializable id, Class<?> clazz, EntityIndexBinding entityIndexBinding) { DocumentBuilderIndexedEntity docBuilder = entityIndexBinding.getDocumentBuilder(); String idInString = idInString( conversionContext, id, clazz, docBuilder ); // 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. return docBuilder.createAddWork( tenantIdentifier, clazz, entity, id, idInString, sessionInitializer, conversionContext ); } private String idInString(ConversionContext conversionContext, Serializable id, Class<?> clazz, DocumentBuilderIndexedEntity docBuilder) { conversionContext.pushProperty( docBuilder.getIdPropertyName() ); try { String idInString = conversionContext.setClass( clazz ).twoWayConversionContext( docBuilder.getIdBridge() ) .objectToString( id ); return idInString; } finally { conversionContext.popProperty(); } } private boolean isNotSkippable(EntityIndexingInterceptor interceptor, Object entity) { if ( interceptor == null ) { return true; } else { return !isSkippable( interceptor.onAdd( entity ) ); } } private boolean isSkippable(IndexingOverride indexingOverride) { switch ( indexingOverride ) { case REMOVE: case SKIP: return true; default: return false; } } private Transaction beginTransaction(Session session) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { Transaction transaction = Helper.getTransactionAndMarkForJoin( session, serviceManager ); transaction.begin(); return transaction; } private Session openSession(Session upperSession) { Session session = upperSession; if ( upperSession == null ) { session = sessionFactory.openSession(); } initSession( session ); return session; } private void initSession(Session session) { session.setFlushMode( FlushMode.MANUAL ); session.setCacheMode( cacheMode ); session.setDefaultReadOnly( true ); } private void close(Session upperSession, Session session) { if ( upperSession == null ) { session.close(); } } @Override public void run(Session upperSession, Tuple tuple) { if ( upperSession == null ) { runInNewTransaction( upperSession, tuple ); } else { runIndexing( upperSession, tuple ); } } /* * Index using the existing session without opening new transactions */ private void runIndexing(Session upperSession, Tuple tuple) { initSession( upperSession ); try { index( upperSession, entity( upperSession, tuple ) ); } catch (Throwable e) { errorHandler.handleException( log.massIndexerUnexpectedErrorMessage(), e ); } finally { log.debug( "finished" ); } } private void runInNewTransaction(Session upperSession, Tuple tuple) { Session session = openSession( upperSession ); try { Transaction transaction = beginTransaction( session ); index( session, entity( session, tuple ) ); transaction.commit(); } catch ( Throwable e ) { errorHandler.handleException( log.massIndexerUnexpectedErrorMessage(), e ); } finally { close( upperSession, session ); log.debug( "finished" ); } } private Object entity(Session session, Tuple tuple) { SessionImplementor sessionImplementor = (SessionImplementor) session; OgmEntityPersister persister = (OgmEntityPersister) sessionFactory.getEntityPersister( indexedType.getName() ); TupleBasedEntityLoader loader = (TupleBasedEntityLoader) persister.getAppropriateLoader( LockOptions.READ, sessionImplementor ); OgmLoadingContext ogmLoadingContext = new OgmLoadingContext(); ogmLoadingContext.setTuples( Collections.singletonList( tuple ) ); List<Object> entities = loader.loadEntitiesFromTuples( sessionImplementor, LockOptions.NONE, ogmLoadingContext ); return entities.get( 0 ); } }