/*
* Hibernate Search, full-text search for your domain model
*
* 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.search.query.hibernate.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.search.engine.integration.impl.ExtendedSearchIntegrator;
import org.hibernate.search.engine.spi.EntityIndexBinding;
import org.hibernate.search.exception.AssertionFailure;
import org.hibernate.search.query.engine.spi.EntityInfo;
import org.hibernate.search.query.engine.spi.TimeoutManager;
/**
* A loader which loads objects of multiple types.
*
* @author Emmanuel Bernard
*/
public class MultiClassesQueryLoader extends AbstractLoader {
private Session session;
private ExtendedSearchIntegrator extendedIntegrator;
private List<RootEntityMetadata> entityMetadata;
private TimeoutManager timeoutManager;
private ObjectInitializer objectInitializer;
@Override
public void init(Session session,
ExtendedSearchIntegrator extendedIntegrator,
ObjectInitializer objectInitializer,
TimeoutManager timeoutManager) {
super.init( session, extendedIntegrator );
this.session = session;
this.extendedIntegrator = extendedIntegrator;
this.timeoutManager = timeoutManager;
this.objectInitializer = objectInitializer;
}
@Override
public boolean isSizeSafe() {
return true; //no user provided criteria
}
public void setEntityTypes(Set<Class<?>> entityTypes) {
List<Class<?>> safeEntityTypes = new ArrayList<>();
// TODO should we go find the root entity for a given class rather than just checking for it's root status?
// root entity could lead to quite inefficient queries in Hibernate when using table per class
if ( entityTypes.size() == 0 ) {
//support all classes
for ( Map.Entry<Class<?>, EntityIndexBinding> entry : extendedIntegrator.getIndexBindings().entrySet() ) {
//get only root entities to limit queries
if ( entry.getValue().getDocumentBuilder().isRoot() ) {
safeEntityTypes.add( entry.getKey() );
}
}
}
else {
safeEntityTypes.addAll( entityTypes );
}
entityMetadata = new ArrayList<>( safeEntityTypes.size() );
for ( Class clazz : safeEntityTypes ) {
entityMetadata.add( new RootEntityMetadata( clazz, extendedIntegrator ) );
}
}
@Override
protected Object executeLoad(EntityInfo entityInfo) {
final Object result = ObjectLoaderHelper.load( entityInfo, session );
timeoutManager.isTimedOut();
return result;
}
@Override
protected List executeLoad(List<EntityInfo> entityInfos) {
LinkedHashMap<EntityInfoLoadKey, Object> idToObjectMap = new LinkedHashMap<>( (int) ( entityInfos.size() / 0.75 ) + 1 );
// split EntityInfo per root entity
Map<RootEntityMetadata, List<EntityInfo>> entityInfoBuckets = new HashMap<>( entityMetadata.size() );
for ( EntityInfo entityInfo : entityInfos ) {
boolean found = false;
final Class<?> clazz = entityInfo.getClazz();
for ( RootEntityMetadata rootEntityInfo : entityMetadata ) {
if ( rootEntityInfo.rootEntity == clazz || rootEntityInfo.mappedSubclasses.contains( clazz ) ) {
List<EntityInfo> bucket = entityInfoBuckets.get( rootEntityInfo );
if ( bucket == null ) {
bucket = new ArrayList<>();
entityInfoBuckets.put( rootEntityInfo, bucket );
}
bucket.add( entityInfo );
found = true;
idToObjectMap.put(
new EntityInfoLoadKey( entityInfo.getClazz(), entityInfo.getId() ),
ObjectInitializer.ENTITY_NOT_YET_INITIALIZED
);
break; //we stop looping for the right bucket
}
}
if ( !found ) {
throw new AssertionFailure( "Could not find root entity for " + clazz );
}
}
// initialize objects by bucket
for ( Map.Entry<RootEntityMetadata, List<EntityInfo>> entry : entityInfoBuckets.entrySet() ) {
final RootEntityMetadata key = entry.getKey();
final List<EntityInfo> value = entry.getValue();
objectInitializer.initializeObjects(
value, idToObjectMap, new ObjectInitializationContext(
null,
key.rootEntity,
extendedIntegrator,
timeoutManager,
session
)
);
timeoutManager.isTimedOut();
}
ArrayList<Object> result = new ArrayList<>( idToObjectMap.size() );
for ( Object o : idToObjectMap.values() ) {
// is the value is null, we had a hit in the Lucene index, but the underlying entity had already been
// removed w/i ORM (HF)
if ( o != ObjectInitializer.ENTITY_NOT_YET_INITIALIZED ) {
result.add( o );
}
}
return result;
}
private static class RootEntityMetadata {
public final Class<?> rootEntity;
public final Set<Class<?>> mappedSubclasses;
RootEntityMetadata(Class<?> rootEntity, ExtendedSearchIntegrator extendedIntegrator) {
this.rootEntity = rootEntity;
EntityIndexBinding provider = extendedIntegrator.getIndexBinding( rootEntity );
if ( provider == null ) {
throw new AssertionFailure( "Provider not found for class: " + rootEntity );
}
this.mappedSubclasses = provider.getDocumentBuilder().getMappedSubclasses();
}
}
}