/*************************************************************************
* Copyright 2009-2015 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.entities;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import com.eucalyptus.util.Pair;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
/**
*
*/
public class EntityCache<E extends AbstractPersistent, TE extends Comparable<TE>> implements Supplier<Iterable<TE>> {
private static final int batchSize = 500;
private final E example;
private final Criterion criterion;
private final Set<String> eagerAssociationPaths;
private final Set<String> lazyAssociationPaths;
private final Function<? super E,TE> transformFunction;
private final ConcurrentMap<Pair<String,Integer>,TE> cache = Maps.newConcurrentMap( );
/**
* Create an entity cache for the given example.
*
* @param example The example object
* @param transformFunction Function to transform to immutable cache format
*/
public EntityCache( final E example,
final Function<? super E,TE> transformFunction ) {
this(
example,
Restrictions.conjunction( ),
Collections.<String>emptySet( ),
Collections.<String>emptySet( ),
transformFunction );
}
/**
* Create an entity cache for the given example.
*
* @param example The example object
* @param criterion Additional criterion
* @param eagerAssociationPaths Paths to be eagerly loaded
* @param lazyAssociationPaths Paths to be lazily (or not) loaded
* @param transformFunction Function to transform to immutable cache format
*/
public EntityCache( final E example,
final Criterion criterion,
final Set<String> eagerAssociationPaths,
final Set<String> lazyAssociationPaths,
final Function<? super E,TE> transformFunction ) {
this.example = example;
this.criterion = criterion;
this.eagerAssociationPaths = eagerAssociationPaths;
this.lazyAssociationPaths = lazyAssociationPaths;
this.transformFunction = transformFunction;
}
@SuppressWarnings( "unchecked" )
private Collection<Pair<String,Integer>> loadVersionMap( ) {
try ( final TransactionResource db = Entities.readOnlyDistinctTransactionFor( example ) ){
final Criteria criteria = Entities.createCriteria( example.getClass( ) )
.add( Example.create( example ) )
.add( criterion )
.setProjection( Projections.projectionList( )
.add( Projections.property( "id" ) )
.add( Projections.property( "version" ) ) );
final List<Object[]> idVersionList = (List<Object[]>) criteria.list( );
final Set<Pair<String,Integer>> results = Sets.newLinkedHashSetWithExpectedSize( idVersionList.size( ) );
Iterables.addAll( results, Iterables.transform( idVersionList, ObjectArrayToStringIntPair.INSTANCE ) );
return results;
}
}
@SuppressWarnings( { "unchecked", "ConstantConditions" } )
private void refresh( ) {
final Collection<Pair<String,Integer>> currentKeys = loadVersionMap( );
cache.keySet( ).retainAll( currentKeys );
currentKeys.removeAll( cache.keySet( ) );
for ( final List<Pair<String,Integer>> keyBatch : Iterables.partition( currentKeys, batchSize ) ) {
try ( final TransactionResource db = Entities.readOnlyDistinctTransactionFor( example ) ) {
final Criteria criteria = Entities.createCriteria( example.getClass( ) )
.add( Example.create( example ) )
.add( criterion )
.setFetchSize( batchSize )
.add( Restrictions.in( "id", Lists.newArrayList( Iterables.transform( keyBatch, Pair.<String, Integer>left( ) ) ) ) );
for ( final String path : eagerAssociationPaths ) criteria.setFetchMode( path, FetchMode.JOIN );
for ( final String path : lazyAssociationPaths ) criteria.setFetchMode( path, FetchMode.SELECT );
final List<E> entities = (List<E> ) criteria.list( );
for ( final E entity : entities ) {
cache.put( Pair.pair( getId( entity ), entity.getVersion( ) ), transformFunction.apply( entity ) );
}
}
}
}
@Override
public Iterable<TE> get( ) {
refresh( );
return Ordering.natural( ).sortedCopy( cache.values( ) );
}
private String getId( final E entity ) {
return Objects.toString( Entities.resolvePrimaryKey( entity ) );
}
private enum ObjectArrayToStringIntPair implements Function<Object[],Pair<String,Integer>> {
INSTANCE;
@Override
public Pair<String, Integer> apply( final Object[] objects ) {
return Pair.pair( String.valueOf( objects[ 0 ] ), ( (Number) objects[ 1 ] ).intValue() );
}
}
}