package org.hibernate.cache.internal;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.cache.spi.QueryKey;
import org.hibernate.cache.spi.QueryResultsCache;
import org.hibernate.cache.spi.QueryResultsRegion;
import org.hibernate.cache.spi.QuerySpacesHelper;
import org.hibernate.cache.spi.TimestampsCache;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.type.Type;
import org.hibernate.type.TypeHelper;
public class QueryResultsCacheImpl implements QueryResultsCache {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QueryResultsCacheImpl.class );
private static final boolean DEBUGGING = LOG.isDebugEnabled();
private static final boolean TRACING = LOG.isTraceEnabled();
private final QueryResultsRegion cacheRegion;
private final TimestampsCache timestampsCache;
QueryResultsCacheImpl(
QueryResultsRegion cacheRegion,
TimestampsCache timestampsCache) {
this.cacheRegion = cacheRegion;
this.timestampsCache = timestampsCache;
}
@Override
public QueryResultsRegion getRegion() {
return cacheRegion;
}
@Override
@SuppressWarnings({ "unchecked" })
public boolean put(
final QueryKey key,
final List results,
final Type[] returnTypes,
final SharedSessionContractImplementor session) throws HibernateException {
if ( DEBUGGING ) {
LOG.debugf( "Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), session.getTransactionStartTimestamp() );
}
final List resultsCopy = CollectionHelper.arrayList( results.size() );
final boolean isSingleResult = returnTypes.length == 1;
for ( Object aResult : results ) {
final Serializable resultRowForCache;
if ( isSingleResult ) {
resultRowForCache = returnTypes[0].disassemble( aResult, session, null );
}
else {
resultRowForCache = TypeHelper.disassemble( (Object[]) aResult, returnTypes, null, session, null );
}
resultsCopy.add( resultRowForCache );
if ( TRACING ) {
logCachedResultRowDetails( returnTypes, aResult );
}
}
if ( TRACING ) {
logCachedResultDetails( key, null, returnTypes, resultsCopy );
}
final CacheItem cacheItem = new CacheItem(
session.getTransactionStartTimestamp(),
resultsCopy
);
try {
session.getEventListenerManager().cachePutStart();
cacheRegion.putIntoCache( key, cacheItem, session );
}
finally {
session.getEventListenerManager().cachePutEnd();
}
return true;
}
private static void logCachedResultDetails(QueryKey key, Set querySpaces, Type[] returnTypes, List result) {
if ( !TRACING ) {
return;
}
LOG.trace( "key.hashCode=" + key.hashCode() );
LOG.trace( "querySpaces=" + querySpaces );
if ( returnTypes == null || returnTypes.length == 0 ) {
LOG.trace(
"Unexpected returnTypes is "
+ ( returnTypes == null ? "null" : "empty" ) + "! result"
+ ( result == null ? " is null" : ".size()=" + result.size() )
);
}
else {
final StringBuilder returnTypeInfo = new StringBuilder();
for ( Type returnType : returnTypes ) {
returnTypeInfo.append( "typename=" )
.append( returnType.getName() )
.append( " class=" )
.append( returnType.getReturnedClass().getName() )
.append( ' ' );
}
LOG.trace( "unexpected returnTypes is " + returnTypeInfo.toString() + "! result" );
}
}
@Override
public List get(
QueryKey key,
Set<Serializable> spaces,
final Type[] returnTypes,
SharedSessionContractImplementor session) {
return get(
key,
QuerySpacesHelper.INSTANCE.toStringArray( spaces ),
returnTypes,
session
);
}
@Override
@SuppressWarnings({ "unchecked" })
public List get(
final QueryKey key,
final String[] spaces,
final Type[] returnTypes,
final SharedSessionContractImplementor session) {
if ( DEBUGGING ) {
LOG.debugf( "Checking cached query results in region: %s", cacheRegion.getName() );
}
final CacheItem cacheItem = getCachedData( key, session );
if ( cacheItem == null ) {
if ( DEBUGGING ) {
LOG.debug( "Query results were not found in cache" );
}
return null;
}
if ( !timestampsCache.isUpToDate( spaces, cacheItem.timestamp, session ) ) {
if ( DEBUGGING ) {
LOG.debug( "Cached query results were not up-to-date" );
}
return null;
}
if ( DEBUGGING ) {
LOG.debug( "Returning cached query results" );
}
final boolean singleResult = returnTypes.length == 1;
for ( int i = 0; i < cacheItem.results.size(); i++ ) {
if ( singleResult ) {
returnTypes[0].beforeAssemble( (Serializable) cacheItem.results.get( i ), session );
}
else {
TypeHelper.beforeAssemble( (Serializable[]) cacheItem.results.get( i ), returnTypes, session );
}
}
return assembleCachedResult( key, cacheItem.results, singleResult, returnTypes, session );
}
private CacheItem getCachedData(QueryKey key, SharedSessionContractImplementor session) {
CacheItem cachedItem = null;
try {
session.getEventListenerManager().cacheGetStart();
cachedItem = (CacheItem) cacheRegion.getFromCache( key, session );
}
finally {
session.getEventListenerManager().cacheGetEnd( cachedItem != null );
}
return cachedItem;
}
@SuppressWarnings("unchecked")
private List assembleCachedResult(
final QueryKey key,
final List cached,
boolean singleResult,
final Type[] returnTypes,
final SharedSessionContractImplementor session) throws HibernateException {
final List result = new ArrayList( cached.size() );
if ( singleResult ) {
for ( Object aCached : cached ) {
result.add( returnTypes[0].assemble( (Serializable) aCached, session, null ) );
}
}
else {
for ( int i = 0; i < cached.size(); i++ ) {
result.add(
TypeHelper.assemble( (Serializable[]) cached.get( i ), returnTypes, session, null )
);
if ( TRACING ) {
logCachedResultRowDetails( returnTypes, result.get( i ) );
}
}
}
return result;
}
private static void logCachedResultRowDetails(Type[] returnTypes, Object result) {
logCachedResultRowDetails(
returnTypes,
( result instanceof Object[] ? (Object[]) result : new Object[] { result } )
);
}
private static void logCachedResultRowDetails(Type[] returnTypes, Object[] tuple) {
if ( !TRACING ) {
return;
}
if ( tuple == null ) {
LOG.tracef(
"tuple is null; returnTypes is %s",
returnTypes == null ? "null" : "Type[" + returnTypes.length + "]"
);
if ( returnTypes != null && returnTypes.length > 1 ) {
LOG.trace(
"Unexpected result tuple! tuple is null; should be Object["
+ returnTypes.length + "]!"
);
}
}
else {
if ( returnTypes == null || returnTypes.length == 0 ) {
LOG.trace(
"Unexpected result tuple! tuple is null; returnTypes is "
+ ( returnTypes == null ? "null" : "empty" )
);
}
LOG.tracef(
"tuple is Object[%s]; returnTypes is %s",
tuple.length,
returnTypes == null ? "null" : "Type[" + returnTypes.length + "]"
);
if ( returnTypes != null && tuple.length != returnTypes.length ) {
LOG.trace(
"Unexpected tuple length! transformer= expected="
+ returnTypes.length + " got=" + tuple.length
);
}
else {
for ( int j = 0; j < tuple.length; j++ ) {
if ( tuple[j] != null && returnTypes != null
&& ! returnTypes[j].getReturnedClass().isInstance( tuple[j] ) ) {
LOG.trace(
"Unexpected tuple value type! transformer= expected="
+ returnTypes[j].getReturnedClass().getName()
+ " got="
+ tuple[j].getClass().getName()
);
}
}
}
}
}
@Override
public String toString() {
return "QueryResultsCache(" + cacheRegion.getName() + ')';
}
public static class CacheItem implements Serializable {
private final long timestamp;
private final List results;
CacheItem(long timestamp, List results) {
this.timestamp = timestamp;
this.results = results;
}
}
}