/************************************************************************* * Copyright 2009-2016 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. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.entities; import java.lang.ref.WeakReference; import java.sql.SQLException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.LockModeType; import javax.persistence.NoResultException; import javax.persistence.NonUniqueResultException; import javax.persistence.OptimisticLockException; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceException; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.persistence.criteria.AbstractQuery; import javax.persistence.criteria.CommonAbstractCriteria; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaDelete; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Expression; import javax.persistence.criteria.From; import javax.persistence.criteria.Join; import javax.persistence.criteria.Subquery; import javax.persistence.metamodel.CollectionAttribute; import javax.persistence.metamodel.ListAttribute; import javax.persistence.metamodel.PluralAttribute; import javax.persistence.metamodel.SetAttribute; import javax.persistence.metamodel.SingularAttribute; import javax.transaction.Synchronization; import org.apache.log4j.Logger; import org.hibernate.Criteria; import org.hibernate.FlushMode; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.StaleObjectStateException; import org.hibernate.Transaction; import org.hibernate.annotations.QueryHints; import org.hibernate.collection.internal.AbstractPersistentCollection; import org.hibernate.criterion.Criterion; import org.hibernate.criterion.Example; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.LogicalConnectionImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.spi.TransactionCoordinator; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.LockAcquisitionException; import org.hibernate.jpa.internal.EntityManagerFactoryImpl; import org.hibernate.proxy.HibernateProxy; import org.hibernate.sql.JoinType; import com.eucalyptus.bootstrap.Databases; import com.eucalyptus.configurable.ConfigurableClass; import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.records.Logs; import com.eucalyptus.system.Ats; import com.eucalyptus.system.Threads; import com.eucalyptus.util.Classes; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.HasNaturalId; import com.eucalyptus.util.LogUtil; import com.eucalyptus.util.NonNullFunction; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import static com.eucalyptus.util.Parameters.checkParam; import static org.hamcrest.Matchers.notNullValue; import groovy.lang.Closure; @ConfigurableClass( root = "bootstrap.tx", description = "Parameters controlling transaction behaviour." ) public class Entities { @ConfigurableField( description = "Maximum number of times a transaction may be retried before giving up.", initial = "10" ) public static Integer CONCURRENT_UPDATE_RETRIES = 10; private static final boolean CLEANUP_TX_SESSION = Boolean.valueOf( System.getProperty( "com.eucalyptus.entities.cleanupTxSession", "true" ) ); private static Cache<String, String> txLog = CacheBuilder.newBuilder().weakKeys().softValues().build(); // No softKeys available for CacheBuilder private static Logger LOG = Logger.getLogger( Entities.class ); private static ThreadLocal<String> txRootThreadLocal = new ThreadLocal<>( ); private static ThreadLocal<ConcurrentMap<String, CascadingTx>> txStateThreadLocal = new ThreadLocal<ConcurrentMap<String, CascadingTx>>( ) { @Override protected ConcurrentMap<String, CascadingTx> initialValue( ) { return Maps.newConcurrentMap( ); } }; static String lookatPersistenceContext( final Object obj ) throws RuntimeException { final Class type = Classes.typeOf( obj ); final Ats ats = Ats.inClassHierarchy( type ); PersistenceContext persistenceContext; if ( !ats.has( PersistenceContext.class ) ) { throw new RuntimeException( "Attempting to create an entity wrapper instance for non persistent type: " + type + ". Class hierarchy contains: \n" + ats.toString( ) ); } else { persistenceContext = ats.get( PersistenceContext.class ); } return persistenceContext.name( ); } public static boolean hasTransaction( final Object obj ) { final String ctx = lookatPersistenceContext( obj ); final CascadingTx tx = txStateThreadLocal.get( ).get( ctx ); if ( tx == null ) { return false; } else if ( tx.isUsable( ) ) { return true; } else { cleanStrandedTx( tx ); return false; } } private static CascadingTx getTransaction( final Object obj ) { if ( hasTransaction( obj ) ) { return txStateThreadLocal.get( ).get( lookatPersistenceContext( obj ) ); } else { throw new NoSuchElementException( "Failed to find active transaction for persistence context: " + lookatPersistenceContext( obj ) + " and object: " + obj ); } } public static void removeTransaction( final CascadingTx tx ) { final String txId = makeTxRootName( tx ); txLog.invalidate( txStateThreadLocal.toString( ) + tx.getRecord( ).getPersistenceContext( ) ); txStateThreadLocal.get( ).remove( tx.getRecord( ).getPersistenceContext( ) ); if ( txId.equals( txStateThreadLocal.get( ) ) ) { for ( final Entry<String, CascadingTx> e : txStateThreadLocal.get( ).entrySet( ) ) { cleanStrandedTx( e.getValue( ) ); } txStateThreadLocal.get( ).clear( ); txStateThreadLocal.remove( ); } } private static void cleanStrandedTx( final CascadingTx txValue ) { LOG.error( "Found stranded transaction: " + txValue.getRecord( ).getPersistenceContext( ) + " started at: " + txValue.getRecord( ).getStack( ) ); try { txValue.rollback( ); } catch ( final Exception ex ) { LOG.trace( ex, ex ); } } private static String makeTxRootName( final CascadingTx tx ) { return txStateThreadLocal.toString( ) + tx.getRecord( ).getPersistenceContext( ); } private static CascadingTx createTransaction( final Object obj ) throws RecoverablePersistenceException, RuntimeException { final String ctx = lookatPersistenceContext( obj ); final CascadingTx ret = new CascadingTx( ctx ); try { ret.begin( ); if ( txRootThreadLocal.get( ) == null ) { final String txId = makeTxRootName( ret ); LOG.trace( "Creating root entry for transaction tree: " + txId + " at: \n" + ret.getRecord().getStack() ); txRootThreadLocal.set( txId ); } txStateThreadLocal.get( ).put( ctx, ret ); return ret; } catch ( RuntimeException ex ) { ret.rollback( ); throw ex; } } /** * @deprecated Use #transactionFor(Object) instead (try-with-resources) * @see #transactionFor(Object) */ @Deprecated public static EntityTransaction get( final Object obj ) { if ( hasTransaction( obj ) ) { final CascadingTx tx = getTransaction( obj ); final EntityTransaction etx = tx.join( ); return etx; } else { return createTransaction( obj ); } } /** * Get a restriction builder for the given entity. * * <p><code> * Entities.query( Entities.restriction( X.class ).equal( X_.name, "example" ) ).list( ) * </code></p> * * @param entityClass The entity class * @param <E> The entity type * @return The builder */ public static <E> EntityRestrictionBuilder<E> restriction( @Nonnull final Class<E> entityClass ) { return new EntityRestrictionBuilder<>( entityClass ); } /** * Get a type-safe query for the given entity. * * <p><code> * Entities.query( X.class ).readonly( ).orderBy( X_.name ).list( ) * </code></p> * * @param entityClass The entity class * @param <E> The entity type * @return The query */ public static <E> EntityCriteriaQuery<E,E> criteriaQuery( @Nonnull final Class<E> entityClass ) { return new EntityCriteriaQuery<>( new SimpleEntityCriteriaQueryContext<>( entityClass ) ); } /** * Get a type-safe query for the given entity restriction. * * <p><code> * Entities.query( Entities.restriction( X.class ).equal( X_.name, "example" ).build( ) ).uniqueResult( ) * </code></p> * * This is a shorter equivalent to: * * <p><code> * Entities.query( X.class ).where( Entities.restriction( X.class ).equal( X_.name, "example" ).build( ) ).uniqueResult( ) * </code></p> * * * @param restriction The entity restriction * @param <E> The entity type * @return The query */ public static <E> EntityCriteriaQuery<E,E> criteriaQuery( @Nonnull final EntityRestriction<E> restriction ) { return criteriaQuery( restriction.getEntityClass( ) ).where( restriction ); } /** * Get a type-safe query for the given entity restriction builder. * * <p><code> * Entities.query( Entities.restriction( X.class ).equal( X_.name, "example" ) ).uniqueResult( ) * </code></p> * * This is a shorter equivalent to: * * <p><code> * Entities.query( X.class ).where( Entities.restriction( X.class ).equal( X_.name, "example" ).build( ) ).uniqueResult( ) * </code></p> * * * @param restriction The entity restriction builder * @param <E> The entity type * @return The query */ public static <E> EntityCriteriaQuery<E,E> criteriaQuery( @Nonnull final EntityRestrictionBuilder<E> restriction ) { return criteriaQuery( restriction.build( ) ); } /** * Get a query for counting instances of the given entity. * * @param entityClass The entity class * @param <E> The entity type * @return The count query */ public static <E> EntityCriteriaQuery<E,Long> count( @Nonnull final Class<E> entityClass ) { return new EntityCriteriaQuery<>( new CountEntityCriteriaQueryContext<>( entityClass ) ); } /** * Get a query for counting instances using the given entity restriction. * * @param entityRestriction The entity restriction * @param <E> The entity type * @return The count query */ public static <E> EntityCriteriaQuery<E,Long> count( @Nonnull final EntityRestriction<E> entityRestriction ) { return count( entityRestriction.getEntityClass( ) ).where( entityRestriction ); } /** * Get a delete for the given entity. * * <p>This is a bulk operation and will not load entities.</p> * * @param entityClass The entity class * @param <E> The entity type * @return The delete */ public static <E> EntityCriteriaDelete<E> delete( @Nonnull final Class<E> entityClass ) { return new EntityCriteriaDelete<>( new EntityCriteriaDeleteContext<>( entityClass ) ); } /** * Get a delete for the given entity restriction. * * <p>This is a bulk operation and will not load entities.</p> * * @param entityRestriction The entity restriction. * @param <E> The entity type * @return The delete */ public static <E> EntityCriteriaDelete<E> delete( @Nonnull final EntityRestriction<E> entityRestriction ) { return delete( entityRestriction.getEntityClass( ) ).where( entityRestriction ); } /** * Create an AutoCloseable transaction for the given object. * * <pre> * try ( TransactionResource transaction = transactionFor( ... ) ) { * ... * transaction.commit( ); * } * </pre> * * The transaction will rollback unless committed. */ public static TransactionResource transactionFor( final Object obj ) { return new TransactionResource( get( obj ) ); } /** * Create an AutoCloseable transaction for the given object. * * <pre> * try ( TransactionResource transaction = distinctTransactionFor( ... ) ) { * ... * transaction.commit( ); * } * </pre> * * <p>The transaction will rollback unless committed.</p> * * <p>This will fail if there is already an active transaction for the * requested context.</p> * * @param obj The object used to determine the transaction context * @return the TransactionResource */ public static TransactionResource distinctTransactionFor( final Object obj ) { if ( hasTransaction( obj ) ) { throw new IllegalStateException( "Found existing transaction for context " + lookatPersistenceContext( obj ) ); } return new TransactionResource( createTransaction( obj ) ); } /** * Create an AutoCloseable read-only transaction for the given object. * * <pre> * try ( TransactionResource transaction = distinctTransactionFor( ... ) ) { * ... * transaction.commit( ); * } * </pre> * * <p>The transaction will rollback unless committed.</p> * * <p>This will fail if there is already an active transaction for the * requested context.</p> * * @param obj The object used to determine the transaction context * @return the TransactionResource */ public static TransactionResource readOnlyDistinctTransactionFor( final Object obj ) { final TransactionResource tx = distinctTransactionFor( obj ); readOnly( obj ); return tx; } /** * Call the given closure in a transaction. * * <p>The closure is passed the related EntityTransaction</p> * * @param obj The object used to determine the transaction context * @param closure The closure to call * @param <R> The closure result type * @return The closure result * @see #get(Object) */ public static <R> R transaction( final Object obj, final Closure<R> closure ) { try ( final TransactionResource transactionResource = transactionFor( obj ) ) { return closure.call( transactionResource ); } } /** * Call the given closure in a transaction. * * <p>The closure is passed the related EntityTransaction</p> * * <p>This will fail if there is already an active transaction for the * requested context.</p> * * @param obj The object used to determine the transaction context * @param closure The closure to call * @param <R> The closure result type * @return The closure result * @see #get(Object) */ public static <R> R distinctTransaction( final Object obj, final Closure<R> closure ) throws IllegalStateException { if ( hasTransaction( obj ) ) { throw new IllegalStateException( "Found existing transaction for context " + lookatPersistenceContext( obj ) ); } return transaction( obj, closure ); } /** * Configure the contextual transaction with optimizations for read-only use. * * WARNING! Transactions can be nested and this will be applied for the * outermost transaction. Read-only distinct transaction methods are * preferred. Use with caution. * * @param object The object used to determine the transaction context * */ public static void readOnly( final Object object ) { final Session session = getTransaction( object ).txState.getSession( ); session.setDefaultReadOnly( true ); session.setFlushMode( FlushMode.MANUAL ); } public static <T> void flush( final T object ) { getTransaction( object ).txState.getEntityManager( ).flush( ); } public static <T> void flushSession( final T object ) { getTransaction( object ).txState.getSession().flush( ); } public static <T> void clearSession( final T object ) { getTransaction( object ).txState.getSession( ).clear( ); } /** * @see #query(T,QueryOptions) */ @Deprecated public static <T> List<T> query( final T example ) { return query( example, false ); } /** * @see #query(T,QueryOptions) */ @SuppressWarnings( { "unchecked", "cast" } ) @Deprecated public static <T> List<T> query( final T example, final boolean readOnly ) { final Example qbe = Example.create( example ); final List<T> resultList = ( List<T> ) getTransaction( example ).getTxState( ).getSession( ) .createCriteria( example.getClass( ) ) .setReadOnly( readOnly ) .setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY ) .setCacheable( true ) .add( qbe ) .list( ); return Lists.newArrayList( Sets.newHashSet( resultList ) ); } /** * Query items matching the given example restricted by the given criterion. * * <P>The caller must have an active transaction for the entity.</P> * * @param example The example object * @param readOnly Use True if the results will not be modified * @param criterion Additional restrictions for the query * @param aliases Any aliases necessary for the given criterion * @param <T> The entity type * @return The result list */ @SuppressWarnings( { "unchecked", "cast" } ) @Deprecated public static <T> List<T> query( final T example, final boolean readOnly, final Criterion criterion, final Map<String,String> aliases ) { return query( example, readOnly, criterion, aliases, false ); } /** * Query items matching the given example restricted by the given criterion. * * <P>The caller must have an active transaction for the entity.</P> * * @param example The example object * @param readOnly Use True if the results will not be modified * @param criterion Additional restrictions for the query * @param aliases Any aliases necessary for the given criterion * @param outerJoins True to use outer joins * @param <T> The entity type * @return The result list */ @SuppressWarnings( { "unchecked", "cast" } ) @Deprecated public static <T> List<T> query( final T example, final boolean readOnly, final Criterion criterion, final Map<String,String> aliases, final boolean outerJoins ) { final Example qbe = Example.create( example ); final Criteria criteria = getTransaction( example ).getTxState( ).getSession( ) .createCriteria( example.getClass( ) ) .setReadOnly( readOnly ) .setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY ) .setCacheable( true ) .add( qbe ) .add( criterion ); for ( final Map.Entry<String,String> aliasEntry : aliases.entrySet() ) { criteria.createAlias( aliasEntry.getKey( ), aliasEntry.getValue( ), outerJoins ? JoinType.LEFT_OUTER_JOIN : JoinType.INNER_JOIN ); } final List<T> resultList = ( List<T> ) criteria.list( ); return Lists.newArrayList( Sets.newHashSet( resultList ) ); } /** * @see #query(T,QueryOptions) */ @SuppressWarnings( { "unchecked", "cast" } ) @Deprecated public static <T> List<T> query( final T example, final boolean readOnly, final int maxResults ) { final Example qbe = Example.create( example ); final List<T> resultList = ( List<T> ) getTransaction( example ).getTxState( ).getSession( ) .createCriteria( example.getClass( ) ) .setReadOnly( readOnly ) .setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY ) .setCacheable( true ) .add( qbe ) .setMaxResults( maxResults ) .setFetchSize( maxResults ) .list( ); return Lists.newArrayList( Sets.newHashSet( resultList ) ); } @SuppressWarnings( "unchecked" ) @Deprecated public static <T> List<T> query( final T example, final QueryOptions options ) { final Example qbe = setOptions( Example.create( example ), options ); final List<T> resultList = ( List<T> ) setOptions( getTransaction( example ).getTxState().getSession() .createCriteria( example.getClass() ), options ) .setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY ) .add( qbe ) .list(); return Lists.newArrayList( Sets.newLinkedHashSet( resultList ) ); } private static Criteria setOptions( final Criteria criteria, final QueryOptions options ) { final Integer maxResults = options.getMaxResults( ); if ( maxResults != null ) { criteria.setMaxResults( maxResults ); } final Integer fetchSize = options.getFetchSize(); if ( fetchSize != null ) { criteria.setFetchSize( fetchSize ); } final Boolean cacheable = options.getCacheable(); if ( cacheable != null ) { criteria.setCacheable( cacheable ); } final Boolean readonly = options.getReadonly(); if ( readonly != null ) { criteria.setReadOnly( readonly ); } final Criterion criterion = options.getCriterion(); if ( criterion != null ) { criteria.add( criterion ); } return criteria; } private static Example setOptions( final Example example, final QueryOptions options ) { if ( options.getMatchMode( ) != null ) { example.enableLike( options.getMatchMode( ) ); } return example; } public static QueryOptionsBuilder queryOptions( ) { return new QueryOptionsBuilder( ); } public interface QueryOptions { @Nullable MatchMode getMatchMode( ); @Nullable Integer getMaxResults( ); @Nullable Integer getFetchSize( ); @Nullable Boolean getCacheable( ); @Nullable Boolean getReadonly( ); @Nullable Criterion getCriterion( ); } @SuppressWarnings( "unused" ) public static final class QueryOptionsBuilder { private MatchMode matchMode; private Integer maxResults; private Integer fetchSize; private Boolean cacheable; private Boolean readonly; private Criterion criterion; public QueryOptionsBuilder withMatchMode( final MatchMode matchMode ) { this.matchMode = matchMode; return this; } public QueryOptionsBuilder withMaxResults( final Integer maxResults ) { this.maxResults = maxResults; return this; } public QueryOptionsBuilder withFetchSize( final Integer fetchSize ) { this.fetchSize = fetchSize; return this; } public QueryOptionsBuilder withCacheable( final Boolean cacheable ) { this.cacheable = cacheable; return this; } public QueryOptionsBuilder withReadonly( final Boolean readonly ) { this.readonly = readonly; return this; } public QueryOptionsBuilder withCriterion( final Criterion criterion ) { this.criterion = criterion; return this; } public QueryOptions build( ) { return new QueryOptions( ) { @Override public MatchMode getMatchMode() { return matchMode; } @Override public Integer getMaxResults() { return maxResults; } @Override public Integer getFetchSize() { return fetchSize; } @Override public Boolean getCacheable() { return cacheable; } @Override public Boolean getReadonly() { return readonly; } @Override public Criterion getCriterion() { return criterion; } }; } } @Deprecated public static <T> T uniqueResult( final T example ) throws TransactionException, NoSuchElementException { try { final Object pk = resolvePrimaryKey( example ); final String natId = resolveNaturalId( example ); if ( pk != null ) { return maybePrimaryKey( example ); } else if ( natId != null ) { return maybeNaturalId( example ); } else { return maybeDefinitelyExample( example ); } } catch ( final NoSuchElementException ex ) { throw ex; } catch ( final RuntimeException ex ) { Logs.extreme( ).trace( ex, ex ); final Exception newEx = PersistenceExceptions.throwFiltered( ex ); throw new TransactionInternalException( newEx.getMessage( ), newEx ); } } public static void evict( final Object obj ) { getTransaction( obj ).getTxState( ).getSession( ).evict( obj ); } public static void evictCache( final Object obj ) { final String ctx = lookatPersistenceContext( obj ); final EntityManagerFactoryImpl emf = PersistenceContexts.getEntityManagerFactory( ctx ); final org.hibernate.Cache cache = emf.getSessionFactory( ).getCache( ); cache.evictQueryRegions( ); cache.evictDefaultQueryRegion( ); cache.evictCollectionRegions( ); cache.evictEntityRegions( ); LOG.debug( "Evicted cache for " + obj ); } private static <T> String resolveNaturalId( final T example ) { if ( ( example instanceof HasNaturalId ) && ( ( ( HasNaturalId ) example ).getNaturalId( ) != null ) ) { return ( ( HasNaturalId ) example ).getNaturalId( ); } else { return null; } } private static <T> T maybeDefinitelyExample( final T example ) throws HibernateException, NoSuchElementException { @SuppressWarnings( "unchecked" ) final T ret = ( T ) Entities.getTransaction( example ) .getTxState( ) .getSession( ) .createCriteria( example.getClass( ) ) .add( Example.create( example ) ) .setCacheable( true ) .setMaxResults( 1 ) .setFetchSize( 1 ) .setFirstResult( 0 ) .uniqueResult( ); if ( ret == null ) { throw new NoSuchElementException( "example: " + LogUtil.dumpObject( example ) ); } return ret; } private static <T> T maybeNaturalId( final T example ) throws HibernateException, NoSuchElementException { final String natId = ( ( HasNaturalId ) example ).getNaturalId( ); @SuppressWarnings( "unchecked" ) final T ret = ( T ) Entities.getTransaction( example ) .getTxState( ) .getSession( ) .createCriteria( example.getClass( ) ) .add( Restrictions.eq( "naturalId", natId ) ) .setCacheable( true ) .uniqueResult( ); if ( ret == null ) { throw new NoSuchElementException( "NaturalId: " + natId ); } return ret; } private static <T> T maybePrimaryKey( final T example ) throws NoSuchElementException { final Object id = resolvePrimaryKey( example ); if ( id == null ) { return null; } else { final T res = ( T ) Entities.getTransaction( example ).getTxState( ).getEntityManager( ).find( example.getClass( ), id ); if ( res == null ) { throw new NoSuchElementException( "@Id: " + id ); } else { return res; } } } static <T> Object resolvePrimaryKey( final T example ) { return Entities.getTransaction( example ).getTxState( ).getEntityManager( ).getEntityManagerFactory( ).getPersistenceUnitUtil( ).getIdentifier( example ); } @Deprecated public static Criteria createCriteria( final Class class1 ) { return getTransaction( class1 ).getTxState( ).getSession( ).createCriteria( class1 ); } @Deprecated public static Criteria createCriteriaUnique( final Class class1 ) { return getTransaction( class1 ).getTxState( ).getSession( ).createCriteria( class1 ).setCacheable( true ).setFetchSize( 1 ).setMaxResults( 1 ).setFirstResult( 0 ); } /** * Invokes underlying persist implementation per jsr-220 * * @throws ConstraintViolationException * @see http://opensource.atlassian.com/projects/hibernate/browse/HHH-1273 */ public static <T> T persist( final T newObject ) throws ConstraintViolationException { try { getTransaction( newObject ).getTxState( ).getEntityManager( ).persist( newObject ); return newObject; } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); throw ex; } } /** * <table> * <tbody> * <tr valign="top"> * <th>Scenario</th> * <th><tt>EntityManager.persist</tt></th> * <th><tt>EntityManager.merge</tt></th> * <th><tt>SessionManager.saveOrUpdate</tt></th> * </tr> * <tr valign="top"> * <th>Object passed was never persisted</th> * * <td>1. Object added to persistence context as new entity<br> * 2. New entity inserted into database at flush/commit</td> * <td>1. State copied to new entity.<br> * 2. New entity added to persistence context<br> * 3. New entity inserted into database at flush/commit<br> * 4. New entity returned</td> * <td>1. Object added to persistence context as new entity<br> * 2. New entity inserted into database at flush/commit</td> * </tr> * <tr valign="top"> * <th>Object was previously persisted, but not loaded in this persistence context</th> * <td>1. <tt>EntityExistsException</tt> thrown (or a <tt>PersistenceException</tt> at * flush/commit)</td> * * <td>2. Existing entity loaded.<br> * 2. State copied from object to loaded entity<br> * 3. Loaded entity updated in database at flush/commit<br> * 4. Loaded entity returned</td> * <td>1. Object added to persistence context<br> * 2. Loaded entity updated in database at flush/commit</td> * </tr> * <tr valign="top"> * <th>Object was previously persisted and already loaded in this persistence context</th> * <td>1. <tt>EntityExistsException</tt> thrown (or a <tt>PersistenceException</tt> at flush or * commit time)</td> * * <td>1. State from object copied to loaded entity<br> * 2. Loaded entity updated in database at flush/commit<br> * 3. Loaded entity returned</td> * <td>1. <tt>NonUniqueObjectException</tt> thrown</td> * </tr> * </tbody> * </table> * * @throws ConstraintViolationException * @throws NoSuchElementException */ public static <T> T merge( final T newObject ) throws ConstraintViolationException { if ( !isPersistent( newObject ) ) { try { return uniqueResult( newObject ); } catch ( final Exception ex ) { return persist( newObject ); } } else { try { final T persistedObject = getTransaction( newObject ).getTxState( ).getEntityManager( ).merge( newObject ); return persistedObject == newObject ? newObject : persistedObject; } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); throw ex; } } } /** * Invokes underlying merge implementation per jsr-220 * * @param object The object to merge * @param <T> The return type * @return The persistent instance for the object */ public static <T> T mergeDirect( final T object ) { try { return getTransaction( object ).getTxState( ).getEntityManager( ).merge( object ); } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); throw ex; } } @Deprecated public <T> T lookupAndClose( final T example ) throws NoSuchElementException { EntityTransaction db; T ret = null; if ( !hasTransaction( example ) ) { db = get( example ); } else { db = getTransaction( example ).join( ); } try { ret = uniqueResult( example ); db.commit( ); } catch ( final TransactionException ex ) { db.rollback( ); throw new NoSuchElementException( ex.getMessage( ) ); } return ret; } public static <T> Function<T, T> merge( ) { return new Function<T, T>( ) { @Override public T apply( final T arg0 ) { return Entities.merge( arg0 ); } }; } public static <T> void refresh( final T newObject ) throws ConstraintViolationException { try { getTransaction( newObject ).getTxState( ).getEntityManager( ).refresh( newObject, ( LockModeType ) null ); } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); throw ex; } } public static <T> void refresh( final T newObject, final LockModeType lockMode ) throws ConstraintViolationException { try { getTransaction( newObject ).getTxState( ).getEntityManager( ).refresh( newObject, lockMode ); } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); throw ex; } } /** * {@inheritDoc Session} */ public static boolean isPersistent( final Object obj ) { if ( !hasTransaction( obj ) ) { return false; } else { return getTransaction( obj ).getTxState( ).getSession( ).contains( obj ); } } /** * Check if a collection or proxy is initialized. * * @param obj The object to test * @return True if initialized */ public static boolean isInitialized( @Nullable final Object obj ) { return obj != null && Hibernate.isInitialized( obj ); } /** * Check if a collection or proxy is readable. * * <p>A lazy object is readable if it has an active transaction or if it is * already initialized.</p> * * @param obj The object to test * @return True if readable */ public static boolean isReadable( @Nullable final Object obj ) { final SessionImplementor sessionImplementor = getSession( obj ); return obj != null && ( ( sessionImplementor != null && sessionImplementor.isOpen( ) ) || isInitialized( obj ) ); } private static SessionImplementor getSession( @Nullable final Object obj ) { SessionImplementor session = null; if ( obj instanceof AbstractPersistentCollection ) { session = ((AbstractPersistentCollection) obj).getSession( ); } else if ( obj instanceof HibernateProxy ) { session = ((HibernateProxy) obj).getHibernateLazyInitializer( ).getSession( ); } return session; } /** * Initialize a lazy proxy / collection, etc. * * <p>Force loading of a lazy object.</p> * * <p>This WILL NOT initialize an entity.</p> * * @param obj The proxy / collection to initialize. */ public static void initialize( @Nullable final Object obj ) { Hibernate.initialize( obj ); } /** * */ public static <T> void delete( final T deleteObject ) { getTransaction( deleteObject ).getTxState( ).getEntityManager( ).remove( deleteObject ); } /** * Delete all entities for the given class. * * @param <T> The entity type * @param deleteClass The entity class */ public static <T> int deleteAll( final Class<T> deleteClass ) { return deleteAllMatching( deleteClass, null, Collections.<String,Object>emptyMap() ); } /** * Delete all matching entities for the given class. * * @param <T> The entity type * @param deleteClass The entity class * @param condition The condition to match */ public static <T> int deleteAllMatching( final Class<T> deleteClass, final String condition, final Map<String,?> parameters ) { try { final Query query = getTransaction( deleteClass ).getTxState().getEntityManager() .createQuery( "DELETE FROM " + deleteClass.getName() + " " + Strings.nullToEmpty(condition)); for ( final Entry<String,?> entry : parameters.entrySet() ) { query.setParameter( entry.getKey(), entry.getValue() ); } return query.executeUpdate(); } catch ( Exception e ) { LOG.error( deleteClass, e ); throw Exceptions.toUndeclared( e ); } } /** * Count the matching entities for the given example. * * @param example The example entity * @return The number of matching entities */ @Deprecated public static long count( final Object example ) { return count( example, Restrictions.conjunction(), Collections.<String,String>emptyMap() ); } /** * Count the matching entities for the given example. * * @param example The example entity * @param criterion Additional restrictions for the query * @param aliases Any aliases necessary for the given criterion * @return The number of matching entities */ @Deprecated public static long count( final Object example, final Criterion criterion, final Map<String,String> aliases ) { final Example qbe = Example.create( example ); final Criteria criteria = getTransaction( example ).getTxState( ).getSession( ) .createCriteria( example.getClass( ) ) .setReadOnly( true ) .setCacheable( false ) .add( qbe ) .add( criterion ) .setProjection( Projections.rowCount() ); for ( final Map.Entry<String,String> aliasEntry : aliases.entrySet() ) { criteria.createAlias( aliasEntry.getKey(), aliasEntry.getValue() ); // inner join by default } final Number count = (Number)criteria.uniqueResult( ); return count.longValue(); } /** * TODO REMOVE THIS, DO NOT USE * @deprecated */ @Deprecated public static void registerClose( final Class<?> emClass ) { final EntityManager entityManager = getTransaction( emClass ).getTxState( ).getEntityManager(); Entities.registerSynchronization( emClass, new Synchronization( ){ @Override public void afterCompletion( final int i ) { entityManager.close(); } @Override public void beforeCompletion( ) { } } ); } public static <T> void registerSynchronization( final Class<T> syncClass, final Synchronization synchronization ) { final Session session = getTransaction( syncClass ).getTxState().getSession(); final Transaction transaction = session.getTransaction(); transaction.registerSynchronization( synchronization ); } /** * Private for a reason. */ private static class CascadingTx implements EntityTransaction { private final TxRecord record; private TxState txState; /** * Private for a reason. * * @see {@link CascadingTx#get(Class)} * @param persistenceContext * @throws RecoverablePersistenceException */ @SuppressWarnings( "unchecked" ) CascadingTx( final String ctx ) throws RecoverablePersistenceException { final String uuid = UUID.randomUUID( ).toString( ); this.record = new TxRecord( ctx, uuid ); try { this.txState = new TxState( ctx ); } catch ( final RuntimeException ex ) { Logs.extreme( ).error( ex, ex ); this.rollback( ); throw PersistenceExceptions.throwFiltered( ex ); } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#getRollbackOnly() */ @Override public boolean getRollbackOnly( ) throws RecoverablePersistenceException { return this.txState == null ? false : this.txState.getRollbackOnly( ); } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#setRollbackOnly() */ @Override public void setRollbackOnly( ) throws RecoverablePersistenceException { if ( this.txState != null ) { this.txState.setRollbackOnly( ); } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#isActive() */ @Override public boolean isActive( ) throws RecoverablePersistenceException { return this.txState == null ? false : this.txState.isActive( ); } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#begin() */ @Override public void begin( ) throws RecoverablePersistenceException { try { this.txState.begin( ); } catch ( final RecoverablePersistenceException ex ) { PersistenceExceptions.throwFiltered( ex ); removeTransaction( this ); } catch ( final RuntimeException ex ) { PersistenceExceptions.throwFiltered( ex ); removeTransaction( this ); throw ex; } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#rollback() */ @Override public void rollback( ) throws RecoverablePersistenceException { removeTransaction( this ); if ( ( this.txState != null ) && this.txState.isActive( ) ) { try { this.txState.rollback( ); this.txState = null; } catch ( final RuntimeException ex ) { Logs.extreme( ).error( ex ); // throw PersistenceExceptions.throwFiltered( ex ); } } else { Logs.extreme( ).debug( "Duplicate call to rollback( )" ); } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#commit() */ @Override public void commit( ) throws RecoverablePersistenceException { removeTransaction( this ); if ( ( this.txState != null ) && this.txState.isActive( ) ) { try { this.txState.commit( ); } catch ( final RuntimeException ex ) { throw PersistenceExceptions.throwFiltered( ex ); } } else if ( Logs.isExtrrreeeme( ) ) { Logs.extreme( ).error( "Duplicate call to commit( ): " + Threads.currentStackString( ) ); } } TxState getTxState( ) { return this.txState; } boolean isUsable( ) { final boolean active = isActive( ); final boolean fresh = ( System.currentTimeMillis( ) - record.getStartTime( ) ) < TimeUnit.MINUTES.toMillis( 1 ); return active && ( fresh || isConnected( ) ); } boolean isConnected( ) { boolean connected = false; final TxState state = this.txState; if ( state != null && state.getSession( ) instanceof SessionImplementor ) { final SessionImplementor sessionImplementor = (SessionImplementor) state.getSession( ); final TransactionCoordinator txCoordinator = sessionImplementor.getTransactionCoordinator( ); if ( txCoordinator != null ) { final JdbcCoordinator jdbcCoordinator = txCoordinator.getJdbcCoordinator( ); if ( jdbcCoordinator != null ) { final LogicalConnectionImplementor logicalConnectionImplementor = jdbcCoordinator.getLogicalConnection( ); try { connected = logicalConnectionImplementor.isOpen( ) && logicalConnectionImplementor.isPhysicallyConnected( ) && !logicalConnectionImplementor.getConnection( ).isClosed( ); } catch ( SQLException e ) { // not connected } } } } return connected; } public EntityTransaction join( ) { return new EntityTransaction( ) { @Override public void setRollbackOnly( ) { } @Override public void rollback( ) { } @Override public void commit( ) { } @Override public void begin( ) { } @Override public boolean isActive( ) { return CascadingTx.this.isActive( ); } @Override public boolean getRollbackOnly( ) { return CascadingTx.this.getRollbackOnly( ); } }; } TxRecord getRecord( ) { return this.record; } private class TxState implements EntityTransaction { private EntityManager em; private EntityTransaction transaction; private final WeakReference<Session> sessionRef; public TxState( final String ctx ) { try { final EntityManagerFactory anemf = PersistenceContexts.getEntityManagerFactory( ctx ); checkParam( anemf, notNullValue() ); this.em = anemf.createEntityManager( ); checkParam( this.em, notNullValue() ); this.transaction = this.em.getTransaction( ); checkParam( this.transaction, notNullValue() ); this.sessionRef = new WeakReference<Session>( ( Session ) this.em.getDelegate( ) ); } catch ( final RuntimeException ex ) { this.doCleanup( ); throw ex; } } private void doCleanup( ) { // transaction if ( this.transaction != null && this.transaction.isActive( ) ) try { this.transaction.rollback( ); } catch ( final RuntimeException ex ) { LOG.warn( ex ); Logs.extreme( ).warn( ex, ex ); } this.transaction = null; if ( CLEANUP_TX_SESSION ) { // sessionRef if ( this.sessionRef != null && ( this.sessionRef.get( ) != null ) ) { this.sessionRef.clear( ); } //em if ( this.em != null && this.em.isOpen( ) ) try { this.em.close( ); } catch ( final RuntimeException ex ) { LOG.warn( ex ); Logs.extreme( ).warn( ex, ex ); } this.em = null; } } EntityManager getEntityManager( ) { return this.em; } Session getSession( ) { return this.sessionRef.get( ); } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#begin() */ @Override public void begin( ) { try { this.transaction.begin( ); } catch ( final RuntimeException ex ) { LOG.warn( ex ); Logs.extreme( ).warn( ex, ex ); doCleanup(); throw ex; } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#commit() */ @Override public void commit( ) { try { this.transaction.commit( ); } catch ( final RuntimeException ex ) { LOG.trace( ex, ex ); Logs.extreme( ).warn( ex, ex ); throw ex; } finally { doCleanup(); } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#getRollbackOnly() */ @Override public boolean getRollbackOnly( ) { return this.transaction.getRollbackOnly( ); } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#isActive() */ @Override public boolean isActive( ) { return this.transaction != null && this.transaction.isActive( ); } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#rollback() */ @Override public void rollback( ) { try { this.transaction.rollback( ); } catch ( final RuntimeException ex ) { LOG.error( ex, ex ); throw ex; } finally { doCleanup(); } } /** * @delegate Do not change semantics here. * @see javax.persistence.EntityTransaction#setRollbackOnly() */ @Override public void setRollbackOnly( ) { this.transaction.setRollbackOnly( ); } } } public static class TxRecord { private final String persistenceContext; private final String uuid; private final Long startTime; private final String stack; TxRecord( final String persistenceContext, final String uuid ) { this.persistenceContext = persistenceContext; this.uuid = uuid; this.stack = Threads.currentStackRange( 0, 32 ); this.startTime = System.currentTimeMillis( ); } public Long getStartTime( ) { return this.startTime; } public String getPersistenceContext( ) { return this.persistenceContext; } public String getUuid( ) { return this.uuid; } String getStack( ) { return this.stack; } } public static class RetryTransactionException extends RuntimeException { private static final long serialVersionUID = 1L; private final Iterable<Class<?>> entitiesToEvict; /** * @param cause Throwable that occurred requiring a retry */ public RetryTransactionException( final Throwable cause ) { this( cause, Collections.<Class<?>>emptyList( ) ); } public RetryTransactionException( final Throwable cause, final Class<?> entityToEvict ) { this( cause, Collections.<Class<?>>singleton( entityToEvict ) ); } public RetryTransactionException( final Throwable cause, final Iterable<Class<?>> entitiesToEvict ) { super( cause ); this.entitiesToEvict = entitiesToEvict; } public Iterable<Class<?>> getEntitiesToEvict( ) { return entitiesToEvict; } } private static class TransactionalFunction<E, D, R> implements Function<D, R> { private Class<E> entityType; private Function<D, R> function; private Integer retries = CONCURRENT_UPDATE_RETRIES; TransactionalFunction( Class<E> entityType, Function<D, R> function, Integer retries ) { this.entityType = entityType; this.function = function; this.retries = retries; } @Override public R apply( final D input ) { RuntimeException rootCause = null; for ( int i = 0; i < retries; i++ ) { try ( final TransactionResource tx = Entities.transactionFor( this.entityType ) ) { R ret = this.function.apply( input ); tx.commit( ); return ret; } catch ( RuntimeException ex ) { if ( Exceptions.isCausedBy( ex, OptimisticLockException.class ) ) { rootCause = Exceptions.findCause( ex, OptimisticLockException.class ); } else if ( Exceptions.isCausedBy( ex, LockAcquisitionException.class ) ) { rootCause = Exceptions.findCause( ex, LockAcquisitionException.class ); } else if ( Exceptions.isCausedBy( ex, StaleObjectStateException.class ) ) { rootCause = Exceptions.findCause( ex, StaleObjectStateException.class ); } else if ( Exceptions.isCausedBy( ex, RetryTransactionException.class ) ) { rootCause = Exceptions.findCause( ex, RetryTransactionException.class ); for ( final Class<?> entityClass : ((RetryTransactionException)rootCause).getEntitiesToEvict( ) ) { Entities.evictCache( entityClass ); } } else { rootCause = ex; Logs.extreme( ).error( ex, ex ); throw ex; } final StaleObjectStateException stale = Exceptions.findCause( ex, StaleObjectStateException.class ); if ( stale != null ) try { Entities.evictCache( Class.forName( stale.getEntityName( ) ) ); } catch ( ClassNotFoundException e ) { /* eviction failure */ } try { TimeUnit.MILLISECONDS.sleep( 20 ); } catch ( InterruptedException ex1 ) { Exceptions.maybeInterrupted( ex1 ); } continue; } } throw ( rootCause != null ? rootCause : new NullPointerException( "BUG: Transaction retry failed but root cause exception is unknown!" ) ); } } public static <T> Supplier<T> asTransaction( final Supplier<T> supplier ) { final List<Class> generics = Classes.genericsToClasses( supplier ); for ( final Class<?> type : generics ) { if ( PersistenceContexts.isPersistentClass( type ) ) { return asTransaction( type, supplier ); } } throw new IllegalArgumentException( "Failed to find generics for provided supplier, cannot make into transaction: " + Threads.currentStackString( ) ); } public static <E, T> Supplier<T> asTransaction( final Class<E> type, final Supplier<T> supplier ) { return asTransaction( type, supplier, CONCURRENT_UPDATE_RETRIES ); } public static <E, T> Supplier<T> asTransaction( final Class<E> type, final Supplier<T> supplier, final Integer retries ) { final Function<Object, T> functionalized = Functions.forSupplier( supplier ); final Function<Object, T> transactionalized = Entities.asTransaction( type, functionalized, retries ); return Suppliers.compose( transactionalized, Suppliers.ofInstance( Void.class ) ); } public static <E, T> Predicate<T> asTransaction( final Predicate<T> predicate ) { final List<Class> generics = Classes.genericsToClasses( predicate ); for ( final Class<?> type : generics ) { if ( PersistenceContexts.isPersistentClass( type ) ) { return asTransaction( type, predicate ); } } throw new IllegalArgumentException( "Failed to find generics for provided predicate, cannot make into transaction: " + Threads.currentStackString( ) ); } public static <E, T> Predicate<T> asDistinctTransaction( final Class<E> type, final Predicate<T> predicate ) { ensureDistinct( type ); return asTransaction( type, predicate ); } public static <E, T> Predicate<T> asTransaction( final Class<E> type, final Predicate<T> predicate ) { return asTransaction( type, predicate, CONCURRENT_UPDATE_RETRIES ); } public static <E, T> Predicate<T> asTransaction( final Class<E> type, final Predicate<T> predicate, final Integer retries ) { final Function<T, Boolean> funcionalized = Functions.forPredicate( predicate ); final Function<T, Boolean> transactionalized = Entities.asTransaction( type, funcionalized, retries ); return new Predicate<T>( ) { @Override public boolean apply( T input ) { return transactionalized.apply( input ); } }; } public static <T, R> Function<T, R> asTransaction( final Function<T, R> function ) { if ( function instanceof TransactionalFunction ) { return function; } else { final List<Class> generics = Classes.genericsToClasses( function ); for ( final Class<?> type : generics ) { if ( PersistenceContexts.isPersistentClass( type ) ) { return asTransaction( type, function ); } } throw new IllegalArgumentException( "Failed to find generics for provided function, cannot make into transaction: " + Threads.currentStackString( ) ); } } public static <E, T, R> Function<T, R> asTransaction( final Class<E> type, final Function<T, R> function ) { if ( function instanceof TransactionalFunction ) { return function; } else { return asTransaction( type, function, CONCURRENT_UPDATE_RETRIES ); } } public static <E, T, R> Function<T, R> asDistinctTransaction( final Class<E> type, final Function<T, R> function ) { ensureDistinct( type ); return asTransaction( type, function ); } public static <E, T, R> Function<T, R> asTransaction( final Class<E> type, final Function<T, R> function, final int retries ) { if ( function instanceof TransactionalFunction ) { return function; } else { return new TransactionalFunction<>( type, function, retries ); } } /** * Commit the transaction if possible, else rollback. * * @param tx The transaction * @return True if committed */ public static boolean commit( EntityTransaction tx ) { boolean committed = false; if ( tx.getRollbackOnly( ) ) { tx.rollback( ); } else if ( Databases.isVolatile( ) ) { tx.rollback( ); } else { tx.commit( ); committed = true; } return committed; } private static void ensureDistinct( final Object type ) { if ( hasTransaction( type ) ) { throw new IllegalStateException( "Found existing transaction for context " + lookatPersistenceContext( type ) ); } } /** * Entity restriction builder. * * <p>Use the fluent API to construct a restriction for querying or deleting * entities.</p> * * @param <E> The restricted entity type * @see #restriction */ @SuppressWarnings( "unused" ) public static class EntityRestrictionBuilder<E> { private final Class<E> entityClass; private final List<EntityRestriction<E>> restrictions = Lists.newArrayList( ); EntityRestrictionBuilder( final Class<E> entityClass ) { this.entityClass = entityClass; } /** * Restriction that all passed restrictions must be true */ @SafeVarargs public final EntityRestrictionBuilder<E> all( EntityRestriction<E>... all ) { restrictions.add( new EntityRestriction.ConjunctionEntityRestriction<>( entityClass, Arrays.asList( all ) ) ); return this; } /** * Restriction that any one of the passed restrictions must be true */ @SafeVarargs public final EntityRestrictionBuilder<E> any( EntityRestriction<E>... any ) { restrictions.add( new EntityRestriction.DisjunctionEntityRestriction<>( entityClass, Arrays.asList( any ) ) ); return this; } /** * @see CriteriaBuilder#equal(Expression, Object) */ public <V> EntityRestrictionBuilder<E> equal( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.EqualPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#notEqual(Expression, Object) */ public <V> EntityRestrictionBuilder<E> notEqual( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.NotEqualPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#equal(Expression, Object) * @see CriteriaBuilder#lower(Expression) */ public EntityRestrictionBuilder<E> equalIgnoreCase( @Nonnull final SingularAttribute<? super E, String> attribute, @Nonnull final String value ) { restrictions.add( new EntityRestriction.EqualIgnoreCasePropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * If the given value is non null it is checked for equality, else this is a no-op. * * @see CriteriaBuilder#equal(Expression, Object) */ public <V> EntityRestrictionBuilder<E> equalIfNonNull( @Nonnull final SingularAttribute<? super E, V> attribute, @Nullable final V value ) { if ( value != null ) { equal( attribute, value ); } return this; } /** * @see CriteriaBuilder#isNull(Expression) */ public <V> EntityRestrictionBuilder<E> isNull( @Nonnull final SingularAttribute<? super E, V> attribute ) { restrictions.add( new EntityRestriction.NullPropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isNotNull(Expression) */ public <V> EntityRestrictionBuilder<E> isNotNull( @Nonnull final SingularAttribute<? super E, V> attribute ) { restrictions.add( new EntityRestriction.NotNullPropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isTrue(Expression) */ public EntityRestrictionBuilder<E> isTrue( @Nonnull final SingularAttribute<? super E, Boolean> attribute ) { restrictions.add( new EntityRestriction.TruePropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isFalse(Expression) */ public EntityRestrictionBuilder<E> isFalse( @Nonnull final SingularAttribute<? super E, Boolean> attribute ) { restrictions.add( new EntityRestriction.FalsePropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isEmpty(Expression) */ public <V,C extends Collection<V>> EntityRestrictionBuilder<E> isEmpty( @Nonnull final PluralAttribute<E, C, V> attribute ) { restrictions.add( new EntityRestriction.EmptyPropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isNotEmpty(Expression) */ public <V,C extends Collection<V>> EntityRestrictionBuilder<E> isNotEmpty( @Nonnull final PluralAttribute<E, C, V> attribute ) { restrictions.add( new EntityRestriction.NotEmptyPropertyEntityRestriction<>( entityClass, attribute ) ); return this; } /** * @see CriteriaBuilder#isMember(Object, Expression) */ public <V,C extends Collection<V>> EntityRestrictionBuilder<E> isMember( @Nonnull final PluralAttribute<E, C, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.MemberPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#isNotMember(Object, Expression) */ public <V,C extends Collection<V>> EntityRestrictionBuilder<E> isNotMember( @Nonnull final PluralAttribute<E, C, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.NotMemberPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#like(Expression, String) */ public EntityRestrictionBuilder<E> like( @Nonnull final SingularAttribute<? super E, String> attribute, @Nonnull final String match ) { restrictions.add( new EntityRestriction.LikePropertyEntityValueRestriction<>( entityClass, attribute, match ) ); return this; } /** * @see CriteriaBuilder#notLike(Expression, String) */ public EntityRestrictionBuilder<E> notLike( @Nonnull final SingularAttribute<? super E, String> attribute, @Nonnull final String match ) { restrictions.add( new EntityRestriction.NotLikePropertyEntityValueRestriction<>( entityClass, attribute, match ) ); return this; } /** * @see CriteriaBuilder#lessThan(Expression, Comparable) */ public EntityRestrictionBuilder<E> before( @Nonnull final SingularAttribute<? super E, Date> attribute, @Nonnull final Date date ) { restrictions.add( new EntityRestriction.DateBeforePropertyEntityValueRestriction<>( entityClass, attribute, date ) ); return this; } /** * @see CriteriaBuilder#greaterThan(Expression, Comparable) */ public EntityRestrictionBuilder<E> after( @Nonnull final SingularAttribute<? super E, Date> attribute, @Nonnull final Date date ) { restrictions.add( new EntityRestriction.DateAfterPropertyEntityValueRestriction<>( entityClass, attribute, date ) ); return this; } /** * @see #after * @see #before */ public EntityRestrictionBuilder<E> between( @Nonnull final SingularAttribute<? super E, Date> attribute, @Nonnull final Date begin, @Nonnull final Date end ) { after( attribute, begin ); before( attribute, end ); return this; } /** * @see CriteriaBuilder#gt(Expression, Number) */ public <V extends Number> EntityRestrictionBuilder<E> gt( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.GreaterThanPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#gt(Expression, Number) */ public <V extends Number> EntityRestrictionBuilder<E> ge( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.GreaterThanOrEqualToPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#lt(Expression, Number) */ public <V extends Number> EntityRestrictionBuilder<E> lt( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.LessThanPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * @see CriteriaBuilder#lt(Expression, Number) */ public <V extends Number> EntityRestrictionBuilder<E> le( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { restrictions.add( new EntityRestriction.LessThanOrEqualToPropertyEntityValueRestriction<>( entityClass, attribute, value ) ); return this; } /** * Build an EntityRestriction for all supplied restrictions. * * @return The restriction conjunction. */ public EntityRestriction<E> build( ) { return new EntityRestriction.ConjunctionEntityRestriction<>( entityClass, restrictions ); } } private static final class SimpleEntityCriteriaQueryContext<E> extends EntityCriteriaQueryContext<E,E,CriteriaQuery<E>> { SimpleEntityCriteriaQueryContext( final Class<E> entityClass ) { super( entityClass, new NonNullFunction<CriteriaBuilder, CriteriaQuery<E>>(){ @Nonnull @Override public CriteriaQuery<E> apply( final CriteriaBuilder builder ) { return builder.createQuery( entityClass ); } } ); } protected void init( ) { this.query.select( from ); } } private static final class CountEntityCriteriaQueryContext<E> extends EntityCriteriaQueryContext<E,Long,CriteriaQuery<Long>> { CountEntityCriteriaQueryContext( final Class<E> entityClass ) { super( entityClass, new NonNullFunction<CriteriaBuilder, CriteriaQuery<Long>>(){ @Nonnull @Override public CriteriaQuery<Long> apply( final CriteriaBuilder builder ) { return builder.createQuery( Long.class ); } } ); } protected void init( ) { this.query.select( builder.countDistinct( from ) ); } } private static final class PropertySubqueryEntityCriteriaQueryContext<E,V> extends EntityCriteriaQueryContext<E,V,Subquery<V>> { private final SingularAttribute<? super E, V> attribute; private final CommonAbstractCriteria criteria; PropertySubqueryEntityCriteriaQueryContext( final CommonAbstractCriteria criteria, final Class<E> entityClass, final SingularAttribute<? super E, V> attribute ) { super( entityClass, new NonNullFunction<CriteriaBuilder, Subquery<V>>(){ @Nonnull @Override public Subquery<V> apply( final CriteriaBuilder builder ) { return criteria.subquery( attribute.getType( ).getJavaType( ) ); } } ); this.criteria = criteria; this.attribute = attribute; this.query.select( from.get( attribute ) ); } protected void init( ) { } } private static abstract class EntityCriteriaContext<E> { protected final Class<E> entityClass; protected final EntityManager em; protected final CriteriaBuilder builder; EntityCriteriaContext( final Class<E> entityClass ) { this.entityClass = entityClass; this.em = getTransaction( entityClass ).getTxState( ).getEntityManager( ); this.builder = this.em.getCriteriaBuilder( ); } protected abstract List<Expression<Boolean>> getRestrictions( ); } private static abstract class EntityCriteriaQueryContext<E,R,Q extends AbstractQuery<R>> extends EntityCriteriaContext<E> { protected final Q query; protected final From<E,E> from; protected final List<Expression<Boolean>> restrictions; EntityCriteriaQueryContext( final Class<E> entityClass, final NonNullFunction<CriteriaBuilder,Q> queryBuilder ) { super( entityClass ); this.query = queryBuilder.apply( builder ); this.from = this.query.from( entityClass ); final javax.persistence.criteria.Predicate where = builder.conjunction( ); this.restrictions = where.getExpressions( ); this.query.where( where ); init( ); } protected abstract void init( ); @Override protected List<Expression<Boolean>> getRestrictions( ) { return restrictions; } } private static class EntityCriteriaDeleteContext<E> extends EntityCriteriaContext<E> { protected final CriteriaDelete<E> delete; protected final From<E,E> from; protected final List<Expression<Boolean>> restrictions; EntityCriteriaDeleteContext( final Class<E> entityClass ) { super( entityClass ); this.delete = this.builder.createCriteriaDelete( entityClass ); this.from = this.delete.from( entityClass ); final javax.persistence.criteria.Predicate where = builder.conjunction( ); this.restrictions = where.getExpressions( ); this.delete.where( where ); } @Override protected List<Expression<Boolean>> getRestrictions( ) { return restrictions; } } @SuppressWarnings( "unused" ) private static final class EntityTypedQueryOptions { private Integer firstResult; private Integer maxResults; private Integer fetchSize; private Boolean readonly; private FlushMode flushMode; public Integer getFirstResult( ) { return firstResult; } public void setFirstResult( final Integer firstResult ) { this.firstResult = firstResult; } public Integer getMaxResults( ) { return maxResults; } public void setMaxResults( final Integer maxResults ) { this.maxResults = maxResults; } public Integer getFetchSize( ) { return fetchSize; } public void setFetchSize( final Integer fetchSize ) { this.fetchSize = fetchSize; } public Boolean getReadonly( ) { return readonly; } public void setReadonly( final Boolean readonly ) { this.readonly = readonly; } public FlushMode getFlushMode( ) { return flushMode; } public void setFlushMode( final FlushMode flushMode ) { this.flushMode = flushMode; } void setOptions( final TypedQuery<?> query ) { if ( firstResult != null ) { query.setFirstResult( firstResult ); } if ( maxResults != null ) { query.setMaxResults( maxResults ); } if ( fetchSize != null ) { query.setHint( QueryHints.FETCH_SIZE, fetchSize ); } if ( readonly != null ) { query.setHint( QueryHints.READ_ONLY, readonly ); } if ( flushMode != null ) { query.setHint( QueryHints.FLUSH_MODE, flushMode ); } } } /** * Callback for construction of subquery * * @param <E> The subquery entity type * @param <R> The subquery result type */ public interface EntityCriteriaSubqueryCallback<E,R> { void restrict( final EntityCriteriaSubquery<E,R> subquery ); } /** * Entity type-safe query * * @param <E> The entity type * @param <R> The query result type */ @SuppressWarnings( "unused" ) public static abstract class EntityCriteriaQueryBase<E,R,C extends AbstractQuery<R>,B extends EntityCriteriaQueryBase<E,R,C,B>> { protected final EntityCriteriaQueryContext<E,R,C> context; protected final EntityTypedQueryOptions options; EntityCriteriaQueryBase( @Nonnull final EntityCriteriaQueryContext<E,R,C> context ) { this.context = context; this.options = new EntityTypedQueryOptions( ); } protected abstract B self( ); /** * Add a where condition to the criteria. * * @param restriction The restriction to add. * @return This criteria query for method chaining. */ public B where( @Nonnull final EntityRestriction<E> restriction ) { context.restrictions.add( restriction.build( context.builder, context.from ) ); return self( ); } /** * Add a where condition to the criteria. * * @param restriction The restriction to add. * @return This criteria query for method chaining. */ public B where( @Nonnull final EntityRestrictionBuilder<E> restriction ) { return where( restriction.build( ) ); } /** * Add a where condition to the criteria. * * @param restrictionFunction The function to build the restriction to add. * @return This criteria query for method chaining. */ public B whereRestriction( @Nonnull final java.util.function.Function<EntityRestrictionBuilder<E>, EntityRestrictionBuilder<E>> restrictionFunction ) { return where( restrictionFunction.apply( Entities.restriction( context.entityClass ) ).build( ) ); } /** * Directly add an equality restriction to the join. * * <p><code> * Entities.query( X.class ) * .join( X.relation ).whereEqual( Y_.name, "example" ).list( ) * </code></p> * <p>This is a shorthand for:</p> * * <p><code> * Entities.query( X.class ) * .join( X.relation ).where( Entities.restriction( Y.class ).equal( Y_.name, "example" ) ).list( ) * </code></p> * * @see Entities.EntityRestrictionBuilder#equal(SingularAttribute, Object) */ public <V> B whereEqual( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { context.restrictions.add( new EntityRestriction.EqualPropertyEntityValueRestriction<>( context.entityClass, attribute, value ).build( context.builder, context.from ) ); return self( ); } /** * Add a subselect contains condition to the criteria. * * @param attribute The attribute to check if contained * @param subqueryEntityClass The subquery entity class * @param subqueryAttribute The attribute of the subquery entity to match * @param subqueryCallback Callback to build subquery constraints. * @param <V> The contained value type * @param <S> The subquery entity type * @return */ public <V,S> B whereIn( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final Class<S> subqueryEntityClass, @Nonnull final SingularAttribute<? super S, V> subqueryAttribute, @Nonnull final EntityCriteriaSubqueryCallback<S,V> subqueryCallback ) { final EntityCriteriaSubquery<S,V> subquery = new EntityCriteriaSubquery<>( new PropertySubqueryEntityCriteriaQueryContext<>( context.query, subqueryEntityClass, subqueryAttribute ) ); subqueryCallback.restrict( subquery ); context.restrictions.add( context.builder.in( context.from.get( attribute ) ).value( subquery.expression( ) ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> B join( @Nonnull final SingularAttribute<? super E, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<E,J> join = context.from.join( attribute ); context.restrictions.add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> B join( @Nonnull final CollectionAttribute<? super E, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<E,J> join = context.from.join( attribute ); context.restrictions.add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> B join( @Nonnull final ListAttribute<? super E, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<E,J> join = context.from.join( attribute ); context.restrictions.add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> B join( @Nonnull final SetAttribute<? super E, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<E,J> join = context.from.join( attribute ); context.restrictions.add( restriction.build( context.builder, join ) ); return self( ); } } public static final class EntityCriteriaSubquery<E,R> extends EntityCriteriaQueryBase<E,R,Subquery<R>,EntityCriteriaSubquery<E,R>> { EntityCriteriaSubquery( @Nonnull final EntityCriteriaQueryContext<E, R, Subquery<R>> context ) { super( context ); } protected EntityCriteriaSubquery<E, R> self() { return this; } Expression<R> expression( ) { return context.query; } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final SingularAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaJoin<>( attribute.getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final CollectionAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final ListAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining. */ public <J> EntityCriteriaJoin<J> join( @Nonnull final SetAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Outer join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> joinLeft( @Nonnull final SingularAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute, javax.persistence.criteria.JoinType.LEFT ); return new EntityCriteriaJoin<>( attribute.getJavaType( ), this.context, join ); } /** * Outer join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> joinLeft( @Nonnull final CollectionAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute, javax.persistence.criteria.JoinType.LEFT ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Outer join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> joinLeft( @Nonnull final ListAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute, javax.persistence.criteria.JoinType.LEFT ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Outer join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining. */ public <J> EntityCriteriaJoin<J> joinLeft( @Nonnull final SetAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute, javax.persistence.criteria.JoinType.LEFT ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } } public static final class EntityCriteriaQuery<E,R> extends EntityCriteriaQueryBase<E,R,CriteriaQuery<R>,EntityCriteriaQuery<E,R>> { EntityCriteriaQuery( @Nonnull final EntityCriteriaQueryContext<E,R, CriteriaQuery<R>> context ) { super( context ); } protected EntityCriteriaQuery<E,R> self( ) { return this; } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,R,EntityCriteriaQuery<E,R>> join( @Nonnull final SingularAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getJavaType( ), this, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,R,EntityCriteriaQuery<E,R>> join( @Nonnull final CollectionAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), this, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,R,EntityCriteriaQuery<E,R>> join( @Nonnull final ListAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), this, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,R,EntityCriteriaQuery<E,R>> join( @Nonnull final SetAttribute<? super E, J> attribute ) { final Join<E,J> join = context.from.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), this, this.context, join ); } /** * Order the query result ascending by the given attribute * * @param attribute The entity attribute to order by * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> orderBy( @Nonnull final SingularAttribute<? super E, ?> attribute ) { context.query.orderBy( context.builder.asc( context.from.get( attribute ) ) ); return this; } /** * Order the query result descending by the given attribute * * @param attribute The entity attribute to order by * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> orderByDesc( @Nonnull final SingularAttribute<? super E, ?> attribute ) { context.query.orderBy( context.builder.desc( context.from.get( attribute ) ) ); return this; } /** * Configure the first result index * * @param firstResult The first result to return * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> firstResult( @Nullable final Integer firstResult ) { this.options.setFirstResult( firstResult ); return this; } /** * Configure the maximum number of results * * @param maxResults The maximum results to return * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> maxResults( @Nullable final Integer maxResults ) { this.options.setMaxResults( maxResults ); return this; } /** * Configure the fetch size for the query. * * @param fetchSize The fetch size to use * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> fetchSize( @Nullable final Integer fetchSize ) { this.options.setFetchSize( fetchSize ); return this; } /** * Configure the flush mode for the query. * * @param flushMode The flush mode to use * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> fetchSize( @Nullable final FlushMode flushMode ) { this.options.setFlushMode( flushMode ); return this; } /** * Configure the read-only flag for the query. * * @param readonly True to set read-only * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> readonly( @Nullable final Boolean readonly ) { this.options.setReadonly( readonly ); return this; } /** * Configure the read-only flag to true for the query. * * @return This criteria query for method chaining. */ public EntityCriteriaQuery<E,R> readonly( ) { return readonly( Boolean.TRUE ); } /** * Get a unique entity for the query. * * @return The entity */ @Nonnull public R uniqueResult( ) { try { return configuredTypedQuery( ).getSingleResult( ); } catch ( final NoResultException | NonUniqueResultException e ) { throw (NoSuchElementException) new NoSuchElementException( ).initCause( e ); } catch ( final PersistenceException e ) { Logs.extreme( ).trace( e, e ); throw e; } } /** * Get a unique entity for the query if available. * * @return The entity option */ @Nonnull public Optional<R> uniqueResultOption( ) { final List<R> results = list( ); final Optional<R> resultOption; if ( results.size( ) == 1 ) { resultOption = Optional.of( results.get( 0 ) ); } else { resultOption = Optional.absent( ); } return resultOption; } /** * List entities for the query. * * @return The list of entities */ @Nonnull public List<R> list( ) { try { return configuredTypedQuery( ).getResultList( ); } catch ( final PersistenceException e ) { Logs.extreme( ).trace( e, e ); throw e; } } private TypedQuery<R> configuredTypedQuery( ) { final TypedQuery<R> typedQuery = context.em.createQuery( context.query ); options.setOptions( typedQuery ); return typedQuery; } } public static final class EntityCriteriaJoin<JE> extends EntityCriteriaBaseJoin<JE,EntityCriteriaJoin<JE>> { EntityCriteriaJoin( @Nonnull Class<JE> joinEntityClass, @Nonnull final EntityCriteriaContext<?> context, @Nonnull final Join<?, JE> join ) { super( joinEntityClass, context, join ); } @Override protected EntityCriteriaJoin<JE> self() { return this; } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final SingularAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaJoin<>( attribute.getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final CollectionAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaJoin<J> join( @Nonnull final ListAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining. */ public <J> EntityCriteriaJoin<J> join( @Nonnull final SetAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaJoin<>( attribute.getElementType( ).getJavaType( ), this.context, join ); } } /** * Entity type-safe join for queries * * @param <JE> The joined entity type * @param <ECQ> The query type */ public static final class EntityCriteriaQueryJoin<JE,ECQR,ECQ extends EntityCriteriaQuery<?,ECQR>> extends EntityCriteriaBaseJoin<JE,EntityCriteriaQueryJoin<JE,ECQR,ECQ>> { private final ECQ entityCriteriaQuery; EntityCriteriaQueryJoin( @Nonnull Class<JE> joinEntityClass, @Nonnull ECQ entityCriteriaQuery, @Nonnull final EntityCriteriaContext<?> context, @Nonnull final Join<?, JE> join ) { super( joinEntityClass, context, join ); this.entityCriteriaQuery = entityCriteriaQuery; } @Override protected EntityCriteriaQueryJoin<JE, ECQR, ECQ> self() { return this; } /** * Get the original query. * * @return The query. */ public ECQ entityCriteriaQuery( ) { return entityCriteriaQuery; } /** * Evaluate the root for this join. * * @see EntityCriteriaQuery#uniqueResult() * @return The unique result entity */ @Nonnull public ECQR uniqueResult( ) { return entityCriteriaQuery( ).uniqueResult( ); } /** * Evaluate the root for this join. * * @see EntityCriteriaQuery#uniqueResultOption() * @return The optional unique result entity */ @Nonnull public Optional<ECQR> uniqueResultOption( ) { return entityCriteriaQuery( ).uniqueResultOption( ); } /** * Evaluate the root for this join. * * @see EntityCriteriaQuery#list() * @return The list of entities */ @Nonnull public List<ECQR> list( ) { return entityCriteriaQuery( ).list( ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,ECQR,ECQ> join( @Nonnull final SingularAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getJavaType( ), entityCriteriaQuery, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,ECQR,ECQ> join( @Nonnull final CollectionAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), entityCriteriaQuery, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining */ public <J> EntityCriteriaQueryJoin<J,ECQR,ECQ> join( @Nonnull final ListAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), entityCriteriaQuery, this.context, join ); } /** * Join on the given attribute and restriction. * * <p>Restrictions should be added to the returned type-safe join.</p> * * @param attribute The entity attribute that defines the relation * @param <J> The joined entity type * @return A criteria join for method chaining. */ public <J> EntityCriteriaQueryJoin<J,ECQR,ECQ> join( @Nonnull final SetAttribute<? super JE, J> attribute ) { final Join<JE,J> join = this.join.join( attribute ); return new EntityCriteriaQueryJoin<>( attribute.getElementType( ).getJavaType( ), entityCriteriaQuery, this.context, join ); } } /** * Entity type-safe join * * @param <JE> The joined entity type */ public static abstract class EntityCriteriaBaseJoin<JE,ECJT> { protected final Class<JE> joinEntityClass; protected final EntityCriteriaContext<?> context; protected final Join<?, JE> join; EntityCriteriaBaseJoin( @Nonnull Class<JE> joinEntityClass, @Nonnull final EntityCriteriaContext<?> context, @Nonnull final Join<?, JE> join ) { this.joinEntityClass = joinEntityClass; this.context = context; this.join = join; } protected abstract ECJT self( ); /** * Add a where condition to the join. * * @param restriction The restriction to add. * @return This criteria join for method chaining. */ public ECJT where( @Nonnull final EntityRestriction<JE> restriction ) { context.getRestrictions( ).add( restriction.build( context.builder, join ) ); return self( ); } /** * Add a where condition to the join. * * @param restriction The restriction to add. * @return This criteria join for method chaining. */ public ECJT where( @Nonnull final EntityRestrictionBuilder<JE> restriction ) { return where( restriction.build( ) ); } /** * Directly add an equality restriction to the query. * * <p><code> * Entities.query( X.class ).whereEquals( X_.type, "example" ).list( ) * </code></p> * <p>This is a shorthand for:</p> * * <p><code> * Entities.query( X.class ).where( Entities.restriction( X.class ).equal( X_.type, "example" ) ).list( ) * </code></p> * * @see Entities.EntityRestrictionBuilder#equal(SingularAttribute, Object) */ public <V> ECJT whereEqual( @Nonnull final SingularAttribute<? super JE, V> attribute, @Nonnull final V value ) { context.getRestrictions( ).add( new EntityRestriction.EqualPropertyEntityValueRestriction<>( joinEntityClass, attribute, value ).build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> ECJT join( @Nonnull final SingularAttribute<? super JE, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<JE,J> join = this.join.join( attribute ); context.getRestrictions( ).add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> ECJT join( @Nonnull final CollectionAttribute<? super JE, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<JE,J> join = this.join.join( attribute ); context.getRestrictions( ).add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> ECJT join( @Nonnull final ListAttribute<? super JE, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<JE,J> join = this.join.join( attribute ); context.getRestrictions( ).add( restriction.build( context.builder, join ) ); return self( ); } /** * Join on the given attribute and restriction. * * @param attribute The entity attribute that defines the relation * @param restriction Restriction on the joined entity * @param <J> The joined entity type * @return This criteria query for method chaining. */ public <J> ECJT join( @Nonnull final SetAttribute<? super JE, J> attribute, @Nonnull final EntityRestriction<J> restriction ) { final Join<JE,J> join = this.join.join( attribute ); context.getRestrictions( ).add( restriction.build( context.builder, join ) ); return self( ); } } /** * Entity type-safe bulk delete * * @param <E> The entity type */ public static final class EntityCriteriaDelete<E> { private final EntityCriteriaDeleteContext<E> context; EntityCriteriaDelete( final EntityCriteriaDeleteContext<E> context ) { this.context = context; } /** * Add a where condition to the criteria. * * @param restriction The restriction to add. * @return This delete criteria for method chaining. */ public EntityCriteriaDelete<E> where( @Nonnull final EntityRestriction<E> restriction ) { context.restrictions.add( restriction.build( context.builder, context.from ) ); return this; } /** * Directly add an equality restriction to the join. * * <p><code> * Entities.query( X.class ) * .join( X.relation ).whereEqual( Y_.name, "example" ).list( ) * </code></p> * <p>This is a shorthand for:</p> * * <p><code> * Entities.query( X.class ) * .join( X.relation ).where( Entities.restriction( Y.class ).equal( Y_.name, "example" ) ).list( ) * </code></p> * * @return This delete criteria for method chaining. * @see Entities.EntityRestrictionBuilder#equal(SingularAttribute, Object) */ public <V> EntityCriteriaDelete<E> whereEqual( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final V value ) { context.restrictions.add( new EntityRestriction.EqualPropertyEntityValueRestriction<>( context.entityClass, attribute, value ).build( context.builder, context.from ) ); return this; } /** * Add a subselect contains condition to the criteria. * * @param attribute The attribute to check if contained * @param subqueryEntityClass The subquery entity class * @param subqueryAttribute The attribute of the subquery entity to match * @param subqueryCallback Callback to build subquery constraints. * @param <V> The contained value type * @param <S> The subquery entity type * @return This delete criteria for method chaining. */ public <V,S> EntityCriteriaDelete<E> whereIn( @Nonnull final SingularAttribute<? super E, V> attribute, @Nonnull final Class<S> subqueryEntityClass, @Nonnull final SingularAttribute<? super S, V> subqueryAttribute, @Nonnull final EntityCriteriaSubqueryCallback<S,V> subqueryCallback ) { final EntityCriteriaSubquery<S,V> subquery = new EntityCriteriaSubquery<>( new PropertySubqueryEntityCriteriaQueryContext<>( context.delete, subqueryEntityClass, subqueryAttribute ) ); subqueryCallback.restrict( subquery ); context.restrictions.add( context.builder.in( context.from.get( attribute ) ).value( subquery.expression( ) ) ); return this; } /** * Perform the deletion. * * @return The count of entities deleted. */ public int delete( ) { return context.em.createQuery( context.delete ).executeUpdate( ); } } }