package org.springframework.data.jpa.repository.query;
import static org.springframework.data.jpa.repository.query.QueryParameterSetter.ErrorHandling.*;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
import javax.persistence.criteria.ParameterExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
interface QueryParameterSetter {
void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor, ErrorHandling errorHandling);
QueryParameterSetter NOOP = (query, values, errorHandling) -> {};
class NamedOrIndexedQueryParameterSetter implements QueryParameterSetter {
private final Function<JpaParametersParameterAccessor, Object> ;
private final Parameter<?> parameter;
private final @Nullable TemporalType temporalType;
NamedOrIndexedQueryParameterSetter(Function<JpaParametersParameterAccessor, Object> valueExtractor,
Parameter<?> parameter, @Nullable TemporalType temporalType) {
Assert.notNull(valueExtractor, "ValueExtractor must not be null!");
this.valueExtractor = valueExtractor;
this.parameter = parameter;
this.temporalType = temporalType;
}
@SuppressWarnings("unchecked")
@Override
public void setParameter(BindableQuery query, JpaParametersParameterAccessor accessor,
ErrorHandling errorHandling) {
Object value = valueExtractor.apply(accessor);
if (temporalType != null) {
if (parameter instanceof ParameterExpression) {
errorHandling.execute(() -> query.setParameter((Parameter<Date>) parameter, (Date) value, temporalType));
} else if (query.hasNamedParameters() && parameter.getName() != null) {
errorHandling.execute(() -> query.setParameter(parameter.getName(), (Date) value, temporalType));
} else {
Integer position = parameter.getPosition();
if (position != null
&& (query.getParameters().size() >= parameter.getPosition()
|| query.registerExcessParameters()
|| errorHandling == LENIENT)) {
errorHandling.execute(() -> query.setParameter(parameter.getPosition(), (Date) value, temporalType));
}
}
} else {
if (parameter instanceof ParameterExpression) {
errorHandling.execute(() -> query.setParameter((Parameter<Object>) parameter, value));
} else if (query.hasNamedParameters() && parameter.getName() != null) {
errorHandling.execute(() -> query.setParameter(parameter.getName(), value));
} else {
Integer position = parameter.getPosition();
if (position != null
&& (query.getParameters().size() >= position
|| errorHandling == LENIENT
|| query.registerExcessParameters())) {
errorHandling.execute(() -> query.setParameter(position, value));
}
}
}
}
}
enum ErrorHandling {
STRICT {
@Override
public void execute(Runnable block) {
block.run();
}
},
LENIENT {
@Override
public void execute(Runnable block) {
try {
block.run();
} catch (RuntimeException rex) {
LOG.info("Silently ignoring", rex);
}
}
};
private static final Logger LOG = LoggerFactory.getLogger(ErrorHandling.class);
abstract void execute(Runnable block);
}
class QueryMetadataCache {
private Map<String, QueryMetadata> cache = Collections.emptyMap();
public QueryMetadata getMetadata(String cacheKey, Query query) {
QueryMetadata queryMetadata = cache.get(cacheKey);
if (queryMetadata == null) {
queryMetadata = new QueryMetadata(query);
Map<String, QueryMetadata> cache;
if (this.cache.isEmpty()) {
cache = Collections.singletonMap(cacheKey, queryMetadata);
} else {
cache = new HashMap<>(this.cache);
cache.put(cacheKey, queryMetadata);
}
synchronized (this) {
this.cache = cache;
}
}
return queryMetadata;
}
}
class QueryMetadata {
private final boolean namedParameters;
private final Set<Parameter<?>> parameters;
private final boolean registerExcessParameters;
QueryMetadata(Query query) {
this.namedParameters = QueryUtils.hasNamedParameter(query);
this.parameters = query.getParameters();
this.registerExcessParameters = query.getParameters().size() == 0
&& unwrapClass(query).getName().startsWith("org.eclipse");
}
QueryMetadata(QueryMetadata metadata) {
this.namedParameters = metadata.namedParameters;
this.parameters = metadata.parameters;
this.registerExcessParameters = metadata.registerExcessParameters;
}
public BindableQuery withQuery(Query query) {
return new BindableQuery(this, query);
}
public Set<Parameter<?>> getParameters() {
return parameters;
}
public boolean hasNamedParameters() {
return this.namedParameters;
}
public boolean registerExcessParameters() {
return this.registerExcessParameters;
}
private static Class<?> unwrapClass(Query query) {
Class<? extends Query> queryType = query.getClass();
try {
return Proxy.isProxyClass(queryType)
? query.unwrap(null).getClass()
: queryType;
} catch (RuntimeException e) {
LoggerFactory.getLogger(QueryMetadata.class).warn("Failed to unwrap actual class for Query proxy.", e);
return queryType;
}
}
}
class BindableQuery extends QueryMetadata {
private final Query query;
private final Query unwrapped;
BindableQuery(QueryMetadata metadata, Query query) {
super(metadata);
this.query = query;
this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
}
private BindableQuery(Query query) {
super(query);
this.query = query;
this.unwrapped = Proxy.isProxyClass(query.getClass()) ? query.unwrap(null) : query;
}
public static BindableQuery from(Query query) {
return new BindableQuery(query);
}
public Query getQuery() {
return query;
}
public <T> Query setParameter(Parameter<T> param, T value) {
return unwrapped.setParameter(param, value);
}
public Query setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
return unwrapped.setParameter(param, value, temporalType);
}
public Query setParameter(String name, Object value) {
return unwrapped.setParameter(name, value);
}
public Query setParameter(String name, Date value, TemporalType temporalType) {
return query.setParameter(name, value, temporalType);
}
public Query setParameter(int position, Object value) {
return unwrapped.setParameter(position, value);
}
public Query setParameter(int position, Date value, TemporalType temporalType) {
return unwrapped.setParameter(position, value, temporalType);
}
}
}