/*
* 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.query.hibernate.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.annotations.common.AssertionFailure;
import org.hibernate.search.engine.spi.EntityIndexBinder;
import org.hibernate.search.engine.spi.SearchFactoryImplementor;
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 SearchFactoryImplementor searchFactoryImplementor;
private List<RootEntityMetadata> entityMatadata;
private TimeoutManager timeoutManager;
private ObjectsInitializer objectsInitializer;
public void init(Session session,
SearchFactoryImplementor searchFactoryImplementor,
ObjectsInitializer objectsInitializer,
TimeoutManager timeoutManager) {
super.init( session, searchFactoryImplementor );
this.session = session;
this.searchFactoryImplementor = searchFactoryImplementor;
this.timeoutManager = timeoutManager;
this.objectsInitializer = objectsInitializer;
}
@Override
public boolean isSizeSafe() {
return true; //no user provided criteria
}
public void setEntityTypes(Set<Class<?>> entityTypes) {
List<Class<?>> safeEntityTypes = new ArrayList<Class<?>>();
//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<?>, EntityIndexBinder> entry : searchFactoryImplementor.getIndexBindingForEntity().entrySet() ) {
//get only root entities to limit queries
if ( entry.getValue().getDocumentBuilder().isRoot() ) {
safeEntityTypes.add( entry.getKey() );
}
}
}
else {
safeEntityTypes.addAll(entityTypes);
}
entityMatadata = new ArrayList<RootEntityMetadata>( safeEntityTypes.size() );
for (Class clazz : safeEntityTypes) {
entityMatadata.add( new RootEntityMetadata( clazz, searchFactoryImplementor ) );
}
}
public Object executeLoad(EntityInfo entityInfo) {
final Object result = ObjectLoaderHelper.load( entityInfo, session );
timeoutManager.isTimedOut();
return result;
}
public List executeLoad(EntityInfo... entityInfos) {
if ( entityInfos.length == 0 ) return Collections.EMPTY_LIST;
if ( entityInfos.length == 1 ) {
final Object entity = load( entityInfos[0] );
if ( entity == null ) {
return Collections.EMPTY_LIST;
}
else {
final List<Object> list = Collections.singletonList( entity );
return list;
}
}
//split EntityInfo per root entity
Map<RootEntityMetadata, List<EntityInfo>> entityinfoBuckets =
new HashMap<RootEntityMetadata, List<EntityInfo>>( entityMatadata.size());
for (EntityInfo entityInfo : entityInfos) {
boolean found = false;
final Class<?> clazz = entityInfo.getClazz();
for (RootEntityMetadata rootEntityInfo : entityMatadata) {
if ( rootEntityInfo.rootEntity == clazz || rootEntityInfo.mappedSubclasses.contains( clazz ) ) {
List<EntityInfo> bucket = entityinfoBuckets.get( rootEntityInfo );
if ( bucket == null ) {
bucket = new ArrayList<EntityInfo>();
entityinfoBuckets.put( rootEntityInfo, bucket );
}
bucket.add( entityInfo );
found = true;
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();
final EntityInfo[] bucketEntityInfos = value.toArray( new EntityInfo[value.size()] );
objectsInitializer.initializeObjects(
bucketEntityInfos,
key.criteria,
key.rootEntity,
searchFactoryImplementor,
timeoutManager,
session);
timeoutManager.isTimedOut();
}
return ObjectLoaderHelper.returnAlreadyLoadedObjectsInCorrectOrder( entityInfos, session );
}
private static class RootEntityMetadata {
public final Class<?> rootEntity;
public final Set<Class<?>> mappedSubclasses;
private final Criteria criteria;
RootEntityMetadata(Class<?> rootEntity, SearchFactoryImplementor searchFactoryImplementor) {
this.rootEntity = rootEntity;
EntityIndexBinder provider = searchFactoryImplementor.getIndexBindingForEntity( rootEntity );
if ( provider == null) throw new AssertionFailure("Provider not found for class: " + rootEntity);
this.mappedSubclasses = provider.getDocumentBuilder().getMappedSubclasses();
this.criteria = null; //default
}
}
}