/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Inc. 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.cache.internal; import javax.persistence.EntityNotFoundException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Set; import org.jboss.logging.Logger; import org.hibernate.HibernateException; import org.hibernate.UnresolvableObjectException; import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.QueryCache; import org.hibernate.cache.spi.QueryKey; import org.hibernate.cache.spi.QueryResultsRegion; import org.hibernate.cache.spi.UpdateTimestampsCache; import org.hibernate.cfg.Settings; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.type.Type; import org.hibernate.type.TypeHelper; /** * The standard implementation of the Hibernate QueryCache interface. This * implementation is very good at recognizing stale query results and * and re-running queries when it detects this condition, recaching the new * results. * * @author Gavin King * @author Steve Ebersole */ public class StandardQueryCache implements QueryCache { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, StandardQueryCache.class.getName()); private QueryResultsRegion cacheRegion; private UpdateTimestampsCache updateTimestampsCache; public void clear() throws CacheException { cacheRegion.evictAll(); } public StandardQueryCache( final Settings settings, final Properties props, final UpdateTimestampsCache updateTimestampsCache, String regionName) throws HibernateException { if ( regionName == null ) { regionName = StandardQueryCache.class.getName(); } String prefix = settings.getCacheRegionPrefix(); if ( prefix != null ) { regionName = prefix + '.' + regionName; } LOG.startingQueryCache(regionName); this.cacheRegion = settings.getRegionFactory().buildQueryResultsRegion( regionName, props ); this.updateTimestampsCache = updateTimestampsCache; } @SuppressWarnings({ "UnnecessaryBoxing", "unchecked" }) public boolean put( QueryKey key, Type[] returnTypes, List result, boolean isNaturalKeyLookup, SessionImplementor session) throws HibernateException { if (isNaturalKeyLookup && result.size() == 0) return false; Long ts = new Long(session.getFactory().getSettings().getRegionFactory().nextTimestamp()); LOG.debugf("Caching query results in region: %s; timestamp=%s", cacheRegion.getName(), ts); List cacheable = new ArrayList(result.size() + 1); logCachedResultDetails(key, null, returnTypes, cacheable); cacheable.add(ts); for (Object aResult : result) { if (returnTypes.length == 1) cacheable.add(returnTypes[0].disassemble(aResult, session, null)); else cacheable.add(TypeHelper.disassemble((Object[])aResult, returnTypes, null, session, null)); logCachedResultRowDetails(returnTypes, aResult); } cacheRegion.put(key, cacheable); return true; } @SuppressWarnings({ "unchecked" }) public List get( QueryKey key, Type[] returnTypes, boolean isNaturalKeyLookup, Set spaces, SessionImplementor session) throws HibernateException { LOG.debugf("Checking cached query results in region: %s", cacheRegion.getName()); List cacheable = ( List ) cacheRegion.get( key ); logCachedResultDetails(key, spaces, returnTypes, cacheable); if ( cacheable == null ) { LOG.debugf("Query results were not found in cache"); return null; } Long timestamp = ( Long ) cacheable.get( 0 ); if ( !isNaturalKeyLookup && !isUpToDate( spaces, timestamp ) ) { LOG.debugf("Cached query results were not up-to-date"); return null; } LOG.debugf("Returning cached query results"); for ( int i = 1; i < cacheable.size(); i++ ) { if ( returnTypes.length == 1 ) { returnTypes[0].beforeAssemble( ( Serializable ) cacheable.get( i ), session ); } else { TypeHelper.beforeAssemble( ( Serializable[] ) cacheable.get( i ), returnTypes, session ); } } List result = new ArrayList( cacheable.size() - 1 ); for ( int i = 1; i < cacheable.size(); i++ ) { try { if ( returnTypes.length == 1 ) { result.add( returnTypes[0].assemble( ( Serializable ) cacheable.get( i ), session, null ) ); } else { result.add( TypeHelper.assemble( ( Serializable[] ) cacheable.get( i ), returnTypes, session, null ) ); } logCachedResultRowDetails(returnTypes, result.get(i - 1)); } catch ( RuntimeException ex ) { if ( isNaturalKeyLookup && ( UnresolvableObjectException.class.isInstance( ex ) || EntityNotFoundException.class.isInstance( ex ) ) ) { //TODO: not really completely correct, since // the uoe could occur while resolving // associations, leaving the PC in an // inconsistent state LOG.debugf("Unable to reassemble cached result set"); cacheRegion.evict( key ); return null; } throw ex; } } return result; } protected boolean isUpToDate(Set spaces, Long timestamp) { LOG.debugf("Checking query spaces are up-to-date: %s", spaces); return updateTimestampsCache.isUpToDate( spaces, timestamp ); } public void destroy() { try { cacheRegion.destroy(); } catch ( Exception e ) { LOG.unableToDestroyQueryCache(cacheRegion.getName(), e.getMessage()); } } public QueryResultsRegion getRegion() { return cacheRegion; } @Override public String toString() { return "StandardQueryCache(" + cacheRegion.getName() + ')'; } private static void logCachedResultDetails(QueryKey key, Set querySpaces, Type[] returnTypes, List result) { if (!LOG.isTraceEnabled()) 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 { StringBuffer returnTypeInfo = new StringBuffer(); for ( int i=0; i<returnTypes.length; i++ ) { returnTypeInfo.append( "typename=" ) .append( returnTypes[ i ].getName() ) .append(" class=" ) .append( returnTypes[ i ].getReturnedClass().getName() ).append(' '); } LOG.trace("unexpected returnTypes is " + returnTypeInfo.toString() + "! result"); } } private static void logCachedResultRowDetails(Type[] returnTypes, Object result) { if (!LOG.isTraceEnabled()) return; logCachedResultRowDetails( returnTypes, ( result instanceof Object[] ? ( Object[] ) result : new Object[] { result } ) ); } private static void logCachedResultRowDetails(Type[] returnTypes, Object[] tuple) { if (!LOG.isTraceEnabled()) return; if ( tuple == null ) { LOG.trace(" tuple is null; returnTypes is " + 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.trace(" tuple is Object[" + tuple.length + "]; returnTypes is Type[" + returnTypes.length + "]"); if (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[j].getReturnedClass().isInstance(tuple[j])) LOG.trace("Unexpected tuple value type! transformer= expected=" + returnTypes[j].getReturnedClass().getName() + " got=" + tuple[j].getClass().getName()); } } } }