/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2009 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors. All
* third-party contributions are distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.ejb;
import static javax.persistence.TemporalType.DATE;
import static javax.persistence.TemporalType.TIME;
import static javax.persistence.TemporalType.TIMESTAMP;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.TransactionRequiredException;
import javax.persistence.TypedQuery;
import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.QueryParameterException;
import org.hibernate.SQLQuery;
import org.hibernate.TypeMismatchException;
import org.hibernate.ejb.util.LockModeTypeHelper;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.query.NamedParameterDescriptor;
import org.hibernate.engine.query.OrdinalParameterDescriptor;
import org.hibernate.hql.QueryExecutionRequestException;
import org.hibernate.impl.AbstractQueryImpl;
import org.jboss.logging.Logger;
/**
* Hibernate implementation of both the {@link Query} and {@link TypedQuery} contracts.
*
* @author <a href="mailto:gavin@hibernate.org">Gavin King</a>
* @author Emmanuel Bernard
* @author Steve Ebersole
*/
public class QueryImpl<X> extends org.hibernate.ejb.AbstractQueryImpl<X> implements TypedQuery<X>, HibernateQuery {
public static final EntityManagerLogger LOG = Logger.getMessageLogger(EntityManagerLogger.class, QueryImpl.class.getName());
private org.hibernate.Query query;
private Set<Integer> jpaPositionalIndices;
private Set<Parameter<?>> parameters;
public QueryImpl(org.hibernate.Query query, AbstractEntityManagerImpl em) {
this( query, em, Collections.<String, Class>emptyMap() );
}
public QueryImpl(
org.hibernate.Query query,
AbstractEntityManagerImpl em,
Map<String,Class> namedParameterTypeRedefinitions) {
super( em );
this.query = query;
extractParameterInfo( namedParameterTypeRedefinitions );
}
@SuppressWarnings({ "unchecked", "RedundantCast" })
private void extractParameterInfo(Map<String,Class> namedParameterTypeRedefinition) {
if ( ! AbstractQueryImpl.class.isInstance( query ) ) {
throw new IllegalStateException( "Unknown query type for parameter extraction" );
}
HashSet<Parameter<?>> parameters = new HashSet<Parameter<?>>();
AbstractQueryImpl queryImpl = AbstractQueryImpl.class.cast( query );
// extract named params
for ( String name : (Set<String>) queryImpl.getParameterMetadata().getNamedParameterNames() ) {
final NamedParameterDescriptor descriptor =
queryImpl.getParameterMetadata().getNamedParameterDescriptor( name );
Class javaType = namedParameterTypeRedefinition.get( name );
if ( javaType != null && mightNeedRedefinition( javaType ) ) {
descriptor.resetExpectedType(
sfi().getTypeResolver().heuristicType( javaType.getName() )
);
}
else if ( descriptor.getExpectedType() != null ) {
javaType = descriptor.getExpectedType().getReturnedClass();
}
final ParameterImpl parameter = new ParameterImpl( name, javaType );
parameters.add( parameter );
if ( descriptor.isJpaStyle() ) {
if ( jpaPositionalIndices == null ) {
jpaPositionalIndices = new HashSet<Integer>();
}
jpaPositionalIndices.add( Integer.valueOf( name ) );
}
}
// extract positional parameters
for ( int i = 0, max = queryImpl.getParameterMetadata().getOrdinalParameterCount(); i < max; i++ ) {
final OrdinalParameterDescriptor descriptor =
queryImpl.getParameterMetadata().getOrdinalParameterDescriptor( i+1 );
ParameterImpl parameter = new ParameterImpl(
i + 1,
descriptor.getExpectedType() == null
? null
: descriptor.getExpectedType().getReturnedClass()
);
parameters.add( parameter );
Integer position = descriptor.getOrdinalPosition();
if (jpaPositionalIndices != null && jpaPositionalIndices.contains(position)) LOG.parameterPositionOccurredAsBothJpaAndHibernatePositionalParameter(position);
}
this.parameters = java.util.Collections.unmodifiableSet( parameters );
}
private SessionFactoryImplementor sfi() {
return (SessionFactoryImplementor) getEntityManager().getFactory().getSessionFactory();
}
private boolean mightNeedRedefinition(Class javaType) {
// for now, only really no for dates/times/timestamps
return java.util.Date.class.isAssignableFrom( javaType );
}
private static class ParameterImpl implements Parameter {
private final String name;
private final Integer position;
private final Class javaType;
private ParameterImpl(String name, Class javaType) {
this.name = name;
this.javaType = javaType;
this.position = null;
}
private ParameterImpl(Integer position, Class javaType) {
this.position = position;
this.javaType = javaType;
this.name = null;
}
public String getName() {
return name;
}
public Integer getPosition() {
return position;
}
public Class getParameterType() {
return javaType;
}
}
public org.hibernate.Query getHibernateQuery() {
return query;
}
@Override
protected int internalExecuteUpdate() {
return query.executeUpdate();
}
@Override
protected void applyMaxResults(int maxResults) {
query.setMaxResults( maxResults );
}
@Override
protected void applyFirstResult(int firstResult) {
query.setFirstResult( firstResult );
}
@Override
protected void applyTimeout(int timeout) {
query.setTimeout( timeout );
}
@Override
protected void applyComment(String comment) {
query.setComment( comment );
}
@Override
protected void applyFetchSize(int fetchSize) {
query.setFetchSize( fetchSize );
}
@Override
protected void applyCacheable(boolean isCacheable) {
query.setCacheable( isCacheable );
}
@Override
protected void applyCacheRegion(String regionName) {
query.setCacheRegion( regionName );
}
@Override
protected void applyReadOnly(boolean isReadOnly) {
query.setReadOnly( isReadOnly );
}
@Override
protected void applyCacheMode(CacheMode cacheMode) {
query.setCacheMode( cacheMode );
}
@Override
protected void applyFlushMode(FlushMode flushMode) {
query.setFlushMode( flushMode );
}
@Override
protected boolean canApplyLockModes() {
return org.hibernate.impl.QueryImpl.class.isInstance( query );
}
@Override
protected void applyAliasSpecificLockMode(String alias, LockMode lockMode) {
( (org.hibernate.impl.QueryImpl) query ).getLockOptions().setAliasSpecificLockMode( alias, lockMode );
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked", "RedundantCast" })
public List<X> getResultList() {
try {
return query.list();
}
catch (QueryExecutionRequestException he) {
throw new IllegalStateException(he);
}
catch( TypeMismatchException e ) {
throw new IllegalArgumentException(e);
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked", "RedundantCast" })
public X getSingleResult() {
try {
boolean mucked = false;
// IMPL NOTE : the mucking with max results here is attempting to help the user from shooting themselves
// in the foot in the case where they have a large query by limiting the query results to 2 max
// SQLQuery cannot be safely paginated, leaving the user's choice here.
if ( getSpecifiedMaxResults() != 1 &&
! ( SQLQuery.class.isAssignableFrom( query.getClass() ) ) ) {
mucked = true;
query.setMaxResults( 2 ); //avoid OOME if the list is huge
}
List<X> result = query.list();
if ( mucked ) {
query.setMaxResults( getSpecifiedMaxResults() );
}
if ( result.size() == 0 ) {
NoResultException nre = new NoResultException( "No entity found for query" );
getEntityManager().handlePersistenceException( nre );
throw nre;
}
else if ( result.size() > 1 ) {
Set<X> uniqueResult = new HashSet<X>(result);
if ( uniqueResult.size() > 1 ) {
NonUniqueResultException nure = new NonUniqueResultException( "result returns more than one elements" );
getEntityManager().handlePersistenceException( nure );
throw nure;
}
else {
return uniqueResult.iterator().next();
}
}
else {
return result.get( 0 );
}
}
catch (QueryExecutionRequestException he) {
throw new IllegalStateException(he);
}
catch( TypeMismatchException e ) {
throw new IllegalArgumentException(e);
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
public <T> TypedQuery<X> setParameter(Parameter<T> param, T value) {
if ( ! parameters.contains( param ) ) {
throw new IllegalArgumentException( "Specified parameter was not found in query" );
}
if ( param.getName() != null ) {
// a named param, for not delegate out. Eventually delegate *into* this method...
setParameter( param.getName(), value );
}
else {
setParameter( param.getPosition(), value );
}
return this;
}
public TypedQuery<X> setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
if ( ! parameters.contains( param ) ) {
throw new IllegalArgumentException( "Specified parameter was not found in query" );
}
if ( param.getName() != null ) {
// a named param, for not delegate out. Eventually delegate *into* this method...
setParameter( param.getName(), value, temporalType );
}
else {
setParameter( param.getPosition(), value, temporalType );
}
return this;
}
public TypedQuery<X> setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) {
if ( ! parameters.contains( param ) ) {
throw new IllegalArgumentException( "Specified parameter was not found in query" );
}
if ( param.getName() != null ) {
// a named param, for not delegate out. Eventually delegate *into* this method...
setParameter( param.getName(), value, temporalType );
}
else {
setParameter( param.getPosition(), value, temporalType );
}
return this;
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(String name, Object value) {
try {
if ( value instanceof Collection ) {
query.setParameterList( name, (Collection) value );
}
else {
query.setParameter( name, value );
}
registerParameterBinding( getParameter( name ), value );
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(String name, Date value, TemporalType temporalType) {
try {
if ( temporalType == DATE ) {
query.setDate( name, value );
}
else if ( temporalType == TIME ) {
query.setTime( name, value );
}
else if ( temporalType == TIMESTAMP ) {
query.setTimestamp( name, value );
}
registerParameterBinding( getParameter( name ), value );
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(String name, Calendar value, TemporalType temporalType) {
try {
if ( temporalType == DATE ) {
query.setCalendarDate( name, value );
}
else if ( temporalType == TIME ) {
throw new IllegalArgumentException( "not yet implemented" );
}
else if ( temporalType == TIMESTAMP ) {
query.setCalendar( name, value );
}
registerParameterBinding( getParameter(name), value );
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(int position, Object value) {
try {
if ( isJpaPositionalParameter( position ) ) {
this.setParameter( Integer.toString( position ), value );
}
else {
query.setParameter( position - 1, value );
registerParameterBinding( getParameter( position ), value );
}
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
private boolean isJpaPositionalParameter(int position) {
return jpaPositionalIndices != null && jpaPositionalIndices.contains( position );
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(int position, Date value, TemporalType temporalType) {
try {
if ( isJpaPositionalParameter( position ) ) {
String name = Integer.toString( position );
this.setParameter( name, value, temporalType );
}
else {
if ( temporalType == DATE ) {
query.setDate( position - 1, value );
}
else if ( temporalType == TIME ) {
query.setTime( position - 1, value );
}
else if ( temporalType == TIMESTAMP ) {
query.setTimestamp( position - 1, value );
}
registerParameterBinding( getParameter( position ), value );
}
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
public TypedQuery<X> setParameter(int position, Calendar value, TemporalType temporalType) {
try {
if ( isJpaPositionalParameter( position ) ) {
String name = Integer.toString( position );
this.setParameter( name, value, temporalType );
}
else {
if ( temporalType == DATE ) {
query.setCalendarDate( position - 1, value );
}
else if ( temporalType == TIME ) {
throw new IllegalArgumentException( "not yet implemented" );
}
else if ( temporalType == TIMESTAMP ) {
query.setCalendar( position - 1, value );
}
registerParameterBinding( getParameter( position ), value );
}
return this;
}
catch (QueryParameterException e) {
throw new IllegalArgumentException( e );
}
catch (HibernateException he) {
throw getEntityManager().convert( he );
}
}
/**
* {@inheritDoc}
*/
public Set<Parameter<?>> getParameters() {
return parameters;
}
/**
* {@inheritDoc}
*/
public Parameter<?> getParameter(String name) {
if ( name == null ) {
throw new IllegalArgumentException( "Name of parameter to locate cannot be null" );
}
for ( Parameter parameter : parameters ) {
if ( name.equals( parameter.getName() ) ) {
return parameter;
}
}
throw new IllegalArgumentException( "Unable to locate parameter named [" + name + "]" );
}
/**
* {@inheritDoc}
*/
public Parameter<?> getParameter(int position) {
if ( isJpaPositionalParameter( position ) ) {
return getParameter( Integer.toString( position ) );
}
else {
for ( Parameter parameter : parameters ) {
if ( parameter.getPosition() != null && position == parameter.getPosition() ) {
return parameter;
}
}
throw new IllegalArgumentException( "Unable to locate parameter with position [" + position + "]" );
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked" })
public <T> Parameter<T> getParameter(String name, Class<T> type) {
Parameter param = getParameter( name );
if ( param.getParameterType() != null ) {
// we were able to determine the expected type during analysis, so validate it here
throw new IllegalArgumentException(
"Parameter type [" + param.getParameterType().getName() +
"] is not assignment compatible with requested type [" +
type.getName() + "]"
);
}
return param;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked" })
public <T> Parameter<T> getParameter(int position, Class<T> type) {
Parameter param = getParameter( position );
if ( param.getParameterType() != null ) {
// we were able to determine the expected type during analysis, so validate it here
throw new IllegalArgumentException(
"Parameter type [" + param.getParameterType().getName() +
"] is not assignment compatible with requested type [" +
type.getName() + "]"
);
}
return param;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings({ "unchecked" })
public <T> T unwrap(Class<T> tClass) {
if ( org.hibernate.Query.class.isAssignableFrom( tClass ) ) {
return (T) query;
}
else {
try {
return (T) this;
}
catch ( ClassCastException cce ) {
PersistenceException pe = new PersistenceException(
"Unsupported unwrap target type [" + tClass.getName() + "]"
);
//It's probably against the spec to not mark the tx for rollback but it will be easier for people
//getEntityManager().handlePersistenceException( pe );
throw pe;
}
}
}
private javax.persistence.LockModeType jpaLockMode = javax.persistence.LockModeType.NONE;
@Override
@SuppressWarnings({ "unchecked" })
public TypedQuery<X> setLockMode(javax.persistence.LockModeType lockModeType) {
if (! getEntityManager().isTransactionInProgress()) {
throw new TransactionRequiredException( "no transaction is in progress" );
}
if ( ! canApplyLockModes() ) {
throw new IllegalStateException( "Not a JPAQL/Criteria query" );
}
this.jpaLockMode = lockModeType;
( (org.hibernate.impl.QueryImpl) query ).getLockOptions().setLockMode(
LockModeTypeHelper.getLockMode( lockModeType )
);
return this;
}
@Override
public javax.persistence.LockModeType getLockMode() {
return jpaLockMode;
}
}