/*
* Copyright 2008-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jpa.repository.query;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.Query;
import javax.persistence.QueryHint;
import javax.persistence.Tuple;
import javax.persistence.TupleElement;
import javax.persistence.TypedQuery;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.CollectionExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ModifyingExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.PagedExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.ProcedureExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.SingleEntityExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.SlicedExecution;
import org.springframework.data.jpa.repository.query.JpaQueryExecution.StreamExecution;
import org.springframework.data.jpa.util.JpaMetamodel;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ResultProcessor;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.util.Assert;
/**
* Abstract base class to implement {@link RepositoryQuery}s.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Mark Paluch
* @author Christoph Strobl
*/
public abstract class AbstractJpaQuery implements RepositoryQuery {
private final JpaQueryMethod method;
private final EntityManager em;
private final JpaMetamodel metamodel;
/**
* Creates a new {@link AbstractJpaQuery} from the given {@link JpaQueryMethod}.
*
* @param method
* @param em
*/
public AbstractJpaQuery(JpaQueryMethod method, EntityManager em) {
Assert.notNull(method, "JpaQueryMethod must not be null!");
Assert.notNull(em, "EntityManager must not be null!");
this.method = method;
this.em = em;
this.metamodel = new JpaMetamodel(em.getMetamodel());
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
*/
public JpaQueryMethod getQueryMethod() {
return method;
}
/**
* Returns the {@link EntityManager}.
*
* @return will never be {@literal null}.
*/
protected EntityManager getEntityManager() {
return em;
}
/**
* Returns the {@link JpaMetamodel}.
*
* @return
*/
protected JpaMetamodel getMetamodel() {
return metamodel;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
*/
public Object execute(Object[] parameters) {
return doExecute(getExecution(), parameters);
}
/**
* @param execution
* @param values
* @return
*/
private Object doExecute(JpaQueryExecution execution, Object[] values) {
Object result = execution.execute(this, values);
ParametersParameterAccessor accessor = new ParametersParameterAccessor(method.getParameters(), values);
ResultProcessor withDynamicProjection = method.getResultProcessor().withDynamicProjection(accessor);
return withDynamicProjection.processResult(result, new TupleConverter(withDynamicProjection.getReturnedType()));
}
protected JpaQueryExecution getExecution() {
if (method.isStreamQuery()) {
return new StreamExecution();
} else if (method.isProcedureQuery()) {
return new ProcedureExecution();
} else if (method.isCollectionQuery()) {
return new CollectionExecution();
} else if (method.isSliceQuery()) {
return new SlicedExecution(method.getParameters());
} else if (method.isPageQuery()) {
return new PagedExecution(method.getParameters());
} else if (method.isModifyingQuery()) {
return method.getClearAutomatically() ? new ModifyingExecution(method, em) : new ModifyingExecution(method, null);
} else {
return new SingleEntityExecution();
}
}
/**
* Applies the declared query hints to the given query.
*
* @param query
* @return
*/
protected <T extends Query> T applyHints(T query, JpaQueryMethod method) {
for (QueryHint hint : method.getHints()) {
applyQueryHint(query, hint);
}
return query;
}
/**
* Protected to be able to customize in sub-classes.
*
* @param query must not be {@literal null}.
* @param hint must not be {@literal null}.
*/
protected <T extends Query> void applyQueryHint(T query, QueryHint hint) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(hint, "QueryHint must not be null!");
query.setHint(hint.name(), hint.value());
}
/**
* Applies the {@link LockModeType} provided by the {@link JpaQueryMethod} to the given {@link Query}.
*
* @param query must not be {@literal null}.
* @param method must not be {@literal null}.
* @return
*/
private Query applyLockMode(Query query, JpaQueryMethod method) {
LockModeType lockModeType = method.getLockModeType();
return lockModeType == null ? query : query.setLockMode(lockModeType);
}
protected ParameterBinder createBinder(Object[] values) {
return new ParameterBinder(getQueryMethod().getParameters(), values);
}
protected Query createQuery(Object[] values) {
return applyLockMode(applyEntityGraphConfiguration(applyHints(doCreateQuery(values), method), method), method);
}
/**
* Configures the {@link javax.persistence.EntityGraph} to use for the given {@link JpaQueryMethod} if the
* {@link EntityGraph} annotation is present.
*
* @param query must not be {@literal null}.
* @param method must not be {@literal null}.
* @return
*/
private Query applyEntityGraphConfiguration(Query query, JpaQueryMethod method) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(method, "JpaQueryMethod must not be null!");
Map<String, Object> hints = Jpa21Utils.tryGetFetchGraphHints(em, method.getEntityGraph(),
getQueryMethod().getEntityInformation().getJavaType());
for (Map.Entry<String, Object> hint : hints.entrySet()) {
query.setHint(hint.getKey(), hint.getValue());
}
return query;
}
protected Query createCountQuery(Object[] values) {
Query countQuery = doCreateCountQuery(values);
return method.applyHintsToCountQuery() ? applyHints(countQuery, method) : countQuery;
}
/**
* Creates a {@link Query} instance for the given values.
*
* @param values must not be {@literal null}.
* @return
*/
protected abstract Query doCreateQuery(Object[] values);
/**
* Creates a {@link TypedQuery} for counting using the given values.
*
* @param values must not be {@literal null}.
* @return
*/
protected abstract Query doCreateCountQuery(Object[] values);
static class TupleConverter implements Converter<Object, Object> {
private final ReturnedType type;
/**
* Creates a new {@link TupleConverter} for the given {@link ReturnedType}.
*
* @param type must not be {@literal null}.
*/
public TupleConverter(ReturnedType type) {
Assert.notNull(type, "Returned type must not be null!");
this.type = type;
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Object convert(Object source) {
if (!(source instanceof Tuple)) {
return source;
}
Tuple tuple = (Tuple) source;
Map<String, Object> result = new HashMap<String, Object>();
List<TupleElement<?>> elements = tuple.getElements();
if (elements.size() == 1) {
Object value = tuple.get(elements.get(0));
if (type.isInstance(value) || value == null) {
return value;
}
}
for (TupleElement<?> element : elements) {
String alias = element.getAlias();
if (alias == null || isIndexAsString(alias)) {
throw new IllegalStateException("No aliases found in result tuple! Make sure your query defines aliases!");
}
result.put(element.getAlias(), tuple.get(element));
}
return result;
}
private static boolean isIndexAsString(String source) {
try {
Integer.parseInt(source);
return true;
} catch (NumberFormatException o_O) {
return false;
}
}
}
}