Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.data.runtime.intercept.AbstractQueryInterceptor Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original 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
*
* https://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 io.micronaut.data.runtime.intercept;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanIntrospector;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.beans.BeanWrapper;
import io.micronaut.core.beans.exceptions.IntrospectionException;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.MutableArgumentValue;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.data.annotation.*;
import io.micronaut.data.exceptions.DataAccessException;
import io.micronaut.data.intercept.DataInterceptor;
import io.micronaut.data.intercept.RepositoryMethodKey;
import io.micronaut.data.intercept.annotation.DataMethod;
import io.micronaut.data.model.*;
import io.micronaut.data.model.query.JoinPath;
import io.micronaut.data.model.query.builder.sql.SqlQueryBuilder;
import io.micronaut.data.model.runtime.*;
import io.micronaut.data.operations.RepositoryOperations;
import io.micronaut.inject.ExecutableMethod;
import javax.annotation.Nonnull;
import java.lang.annotation.Annotation;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_PAGE_SIZE;
import static io.micronaut.data.model.query.builder.QueryBuilder.VARIABLE_PATTERN;
/**
* Abstract interceptor that executes a {@link io.micronaut.data.annotation.Query}.
* @param The declaring type
* @param The return type
* @since 1.0
* @author graemerocher
*/
public abstract class AbstractQueryInterceptor implements DataInterceptor {
private static final String PREDATOR_ANN_NAME = DataMethod.class.getName();
private static final int[] EMPTY_INT_ARRAY = new int[0];
protected final RepositoryOperations operations;
private final ConcurrentMap lastUpdatedTypes = new ConcurrentHashMap<>(10);
private final ConcurrentMap findQueries = new ConcurrentHashMap<>(50);
private final ConcurrentMap countQueries = new ConcurrentHashMap<>(50);
/**
* Default constructor.
* @param operations The operations
*/
protected AbstractQueryInterceptor(@NonNull RepositoryOperations operations) {
ArgumentUtils.requireNonNull("operations", operations);
this.operations = operations;
}
/**
* Prepares a query for the given context.
*
* @param key The method key
* @param context The context
* @return The query
*/
protected final PreparedQuery, ?> prepareQuery(RepositoryMethodKey key, MethodInvocationContext context) {
return prepareQuery(key, context, null);
}
/**
* Prepares a query for the given context.
*
* @param The result generic type
* @param methodKey The method key
* @param context The context
* @param resultType The result type
* @return The query
*/
protected final PreparedQuery, RT> prepareQuery(
RepositoryMethodKey methodKey,
MethodInvocationContext context,
Class resultType) {
validateNullArguments(context);
StoredQuery, RT> storedQuery = findQueries.get(methodKey);
if (storedQuery == null) {
Class> rootEntity = context.classValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_ROOT_ENTITY)
.orElseThrow(() -> new IllegalStateException("No root entity present in method"));
if (resultType == null) {
//noinspection unchecked
resultType = (Class) context.classValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_RESULT_TYPE)
.orElse(rootEntity);
}
String query = context.stringValue(Query.class).orElseThrow(() ->
new IllegalStateException("No query present in method")
);
storedQuery = new DefaultStoredQuery<>(
context.getExecutableMethod(),
resultType,
rootEntity,
query,
DataMethod.META_MEMBER_PARAMETER_BINDING,
false
);
findQueries.put(methodKey, storedQuery);
}
Pageable pageable = storedQuery.hasPageable() ? getPageable(context) : Pageable.UNPAGED;
String query = storedQuery.getQuery();
return new DefaultPreparedQuery<>(
context,
storedQuery,
query,
pageable,
storedQuery.isDtoProjection()
);
}
/**
* Prepares a query for the given context.
*
* @param methodKey The method key
* @param context The context
* @return The query
*/
protected final PreparedQuery, Number> prepareCountQuery(RepositoryMethodKey methodKey, @NonNull MethodInvocationContext context) {
ExecutableMethod executableMethod = context.getExecutableMethod();
StoredQuery, Long> storedQuery = countQueries.get(methodKey);
if (storedQuery == null) {
String query = context.stringValue(Query.class, DataMethod.META_MEMBER_COUNT_QUERY).orElseThrow(() ->
new IllegalStateException("No query present in method")
);
Class rootEntity = getRequiredRootEntity(context);
storedQuery = new DefaultStoredQuery(
executableMethod,
Long.class,
rootEntity,
query,
DataMethod.META_MEMBER_PARAMETER_BINDING,
true
);
countQueries.put(methodKey, storedQuery);
}
Pageable pageable = storedQuery.hasPageable() ? getPageable(context) : Pageable.UNPAGED;
//noinspection unchecked
return new DefaultPreparedQuery(
context,
storedQuery,
storedQuery.getQuery(),
pageable,
false
);
}
/**
* Obtains the root entity or throws an exception if it not available.
* @param context The context
* @return The root entity type
* @throws IllegalStateException If the root entity is unavailable
*/
@NonNull
protected Class> getRequiredRootEntity(MethodInvocationContext context) {
Class aClass = context.classValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_ROOT_ENTITY).orElse(null);
if (aClass != null) {
return aClass;
} else {
final AnnotationValue ann = context.getDeclaredAnnotation(PREDATOR_ANN_NAME);
if (ann != null) {
aClass = ann.classValue(DataMethod.META_MEMBER_ROOT_ENTITY).orElse(null);
if (aClass != null) {
return aClass;
}
}
throw new IllegalStateException("No root entity present in method");
}
}
/**
* Retrieve a parameter in the given role for the given type.
* @param context The context
* @param role The role
* @param type The type
* @param The generic type
* @return An optional result
*/
private Optional getParameterInRole(MethodInvocationContext, ?> context, @NonNull String role, @NonNull Class type) {
return context.stringValue(PREDATOR_ANN_NAME, role).flatMap(name -> {
RT parameterValue = null;
Map> params = context.getParameters();
MutableArgumentValue> arg = params.get(name);
if (arg != null) {
Object o = arg.getValue();
if (o != null) {
if (type.isInstance(o)) {
//noinspection unchecked
parameterValue = (RT) o;
} else {
parameterValue = ConversionService.SHARED
.convert(o, type).orElse(null);
}
}
}
return Optional.ofNullable(parameterValue);
});
}
/**
* Resolves the {@link Pageable} for the given context.
* @param context The pageable
* @return The pageable or null
*/
@NonNull
protected Pageable getPageable(MethodInvocationContext, ?> context) {
Pageable pageable = getParameterInRole(context, TypeRole.PAGEABLE, Pageable.class).orElse(null);
if (pageable == null) {
Sort sort = getParameterInRole(context, TypeRole.SORT, Sort.class).orElse(null);
if (sort != null) {
int max = context.intValue(PREDATOR_ANN_NAME, META_MEMBER_PAGE_SIZE).orElse(-1);
int pageIndex = context.intValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_PAGE_INDEX).orElse(0);
if (max > 0) {
pageable = Pageable.from(pageIndex, max, sort);
} else {
pageable = Pageable.from(sort);
}
} else {
int max = context.intValue(PREDATOR_ANN_NAME, META_MEMBER_PAGE_SIZE).orElse(-1);
if (max > -1) {
return Pageable.from(0, max);
}
}
}
return pageable != null ? pageable : Pageable.UNPAGED;
}
/**
* Return whether the metadata indicates the instance is nullable.
* @param metadata The metadata
* @return True if it is nullable
*/
protected boolean isNullable(@NonNull AnnotationMetadata metadata) {
return metadata
.getDeclaredAnnotationNames()
.stream()
.anyMatch(n -> NameUtils.getSimpleName(n).equalsIgnoreCase("nullable"));
}
/**
* Looks up the entity to persist from the execution context, or throws an exception.
* @param context The context
* @return The entity
*/
protected @NonNull Object getRequiredEntity(MethodInvocationContext context) {
String entityParam = context.stringValue(PREDATOR_ANN_NAME, TypeRole.ENTITY)
.orElseThrow(() -> new IllegalStateException("No entity parameter specified"));
Object o = context.getParameterValueMap().get(entityParam);
if (o == null) {
throw new IllegalArgumentException("Entity argument cannot be null");
}
return o;
}
private Map buildParameterValues(MethodInvocationContext context, StoredQuery, RT> storedQuery) {
Map parameterValueMap = context.getParameterValueMap();
Map, ?> parameterBinding = storedQuery.getParameterBinding();
Map parameterValues = new HashMap<>(parameterBinding.size());
for (Map.Entry entry : parameterBinding.entrySet()) {
Object name = entry.getKey();
String argument = (String) entry.getValue();
storeInParameterValues(context, storedQuery, parameterValueMap, name, argument, parameterValues);
}
return parameterValues;
}
private void storeInParameterValues(
MethodInvocationContext context,
StoredQuery, RT> storedQuery,
Map namedValues,
Object index,
String argument,
Map parameterValues) {
if (namedValues.containsKey(argument)) {
parameterValues.put(index, namedValues.get(argument));
} else {
String v = storedQuery.getLastUpdatedProperty();
if (v != null && v.equals(argument)) {
Class> rootEntity = storedQuery.getRootEntity();
Class> lastUpdatedType = getLastUpdatedType(rootEntity, v);
if (lastUpdatedType == null) {
throw new IllegalStateException("Could not establish last updated time for entity: " + rootEntity);
}
Object timestamp = ConversionService.SHARED.convert(OffsetDateTime.now(), lastUpdatedType).orElse(null);
if (timestamp == null) {
throw new IllegalStateException("Unsupported date type: " + lastUpdatedType);
}
parameterValues.put(index, timestamp);
} else {
int i = argument.indexOf('.');
if (i > -1) {
String argumentName = argument.substring(0, i);
Object o = namedValues.get(argumentName);
if (o != null) {
try {
BeanWrapper wrapper = BeanWrapper.getWrapper(o);
String prop = argument.substring(i + 1);
Object val = wrapper.getRequiredProperty(prop, Object.class);
parameterValues.put(index, val);
} catch (IntrospectionException e) {
throw new DataAccessException("Embedded value [" + o + "] should be annotated with introspected");
}
}
} else {
Optional named = Arrays.stream(context.getArguments())
.filter(arg -> {
String n = arg.getAnnotationMetadata().stringValue(Parameter.class).orElse(arg.getName());
return n.equals(argument);
})
.findFirst();
if (named.isPresent()) {
parameterValues.put(index, namedValues.get(named.get().getName()));
} else {
throw new IllegalArgumentException("Missing query arguments: " + argument);
}
}
}
}
}
private Class> getLastUpdatedType(Class> rootEntity, String property) {
Class> type = lastUpdatedTypes.get(rootEntity);
if (type == null) {
type = BeanIntrospector.SHARED
.findIntrospection(rootEntity)
.flatMap(bp -> bp.getProperty(property))
.map(BeanProperty::getType).orElse(null);
if (type != null) {
lastUpdatedTypes.put(rootEntity, type);
}
}
return type;
}
/**
* Instantiate the given entity for the given parameter values.
*
* @param rootEntity The entity
* @param parameterValues The parameter values
* @return The entity
* @throws IllegalArgumentException if the entity cannot be instantiated due to an illegal argument
*/
protected @NonNull Object instantiateEntity(@NonNull Class> rootEntity, @NonNull Map parameterValues) {
PersistentEntity entity = operations.getEntity(rootEntity);
BeanIntrospection> introspection = BeanIntrospection.getIntrospection(rootEntity);
Argument>[] constructorArguments = introspection.getConstructorArguments();
Object instance;
if (ArrayUtils.isNotEmpty(constructorArguments)) {
Object[] arguments = new Object[constructorArguments.length];
for (int i = 0; i < constructorArguments.length; i++) {
Argument> argument = constructorArguments[i];
String argumentName = argument.getName();
Object v = parameterValues.get(argumentName);
AnnotationMetadata argMetadata = argument.getAnnotationMetadata();
if (v == null && !PersistentProperty.isNullableMetadata(argMetadata)) {
PersistentProperty prop = entity.getPropertyByName(argumentName);
if (prop == null || prop.isRequired()) {
throw new IllegalArgumentException("Argument [" + argumentName + "] cannot be null");
}
}
arguments[i] = v;
}
instance = introspection.instantiate(arguments);
} else {
instance = introspection.instantiate();
}
BeanWrapper wrapper = BeanWrapper.getWrapper(instance);
Collection extends PersistentProperty> persistentProperties = entity.getPersistentProperties();
for (PersistentProperty prop : persistentProperties) {
if (!prop.isReadOnly() && !prop.isGenerated()) {
String propName = prop.getName();
if (parameterValues.containsKey(propName)) {
Object v = parameterValues.get(propName);
if (v == null && !prop.isOptional()) {
throw new IllegalArgumentException("Argument [" + propName + "] cannot be null");
}
wrapper.setProperty(propName, v);
} else if (prop.isRequired()) {
final Optional p = wrapper.getProperty(propName, Object.class);
if (!p.isPresent()) {
throw new IllegalArgumentException("Argument [" + propName + "] cannot be null");
}
}
}
}
return instance;
}
/**
* Convert a number argument if necessary.
* @param number The number
* @param argument The argument
* @return The result
*/
protected @Nullable Number convertNumberArgumentIfNecessary(Number number, Argument> argument) {
Argument> firstTypeVar = argument.getFirstTypeVariable().orElse(Argument.of(Long.class));
Class> type = firstTypeVar.getType();
if (type == Object.class || type == Void.class) {
return null;
}
if (number == null) {
number = 0;
}
if (!type.isInstance(number)) {
return (Number) ConversionService.SHARED.convert(number, firstTypeVar)
.orElseThrow(() -> new IllegalStateException("Unsupported number type for return type: " + firstTypeVar));
} else {
return number;
}
}
/**
* Get the paged query for the given context.
* @param context The contet
* @param The entity type
* @return The paged query
*/
protected @NonNull PagedQuery getPagedQuery(@NonNull MethodInvocationContext context) {
@SuppressWarnings("unchecked") Class rootEntity = (Class) getRequiredRootEntity(context);
Pageable pageable = getPageable(context);
return new DefaultPagedQuery<>(context.getExecutableMethod(), rootEntity, pageable);
}
/**
* Get the batch oepration for the given context.
* @param context The context
* @param iterable The iterable
* @param The entity type
* @return The paged query
*/
protected @NonNull BatchOperation getBatchOperation(@NonNull MethodInvocationContext context, @NonNull Iterable iterable) {
@SuppressWarnings("unchecked") Class rootEntity = (Class) getRequiredRootEntity(context);
return getBatchOperation(context, rootEntity, iterable);
}
/**
* Get the batch operation for the given context.
* @param The entity type
* @param context The context
* @param rootEntity The root entity
* @param iterable The iterable
* @return The paged query
*/
protected BatchOperation getBatchOperation(@NonNull MethodInvocationContext context, Class rootEntity, @NonNull Iterable iterable) {
return new DefaultBatchOperation<>(context, rootEntity, iterable);
}
/**
* Get the batch operation for the given context.
* @param context The context
* @param The entity type
* @return The paged query
*/
protected @NonNull BatchOperation getBatchOperation(@NonNull MethodInvocationContext context) {
@SuppressWarnings("unchecked") Class rootEntity = (Class) getRequiredRootEntity(context);
return getBatchOperation(context, rootEntity);
}
/**
* Get the batch operation for the given context.
* @param context The context
* @param rootEntity The root entity
* @param The entity type
* @return The paged query
*/
protected BatchOperation getBatchOperation(@NonNull MethodInvocationContext context, @NonNull Class rootEntity) {
return new AllBatchOperation<>(context, rootEntity);
}
/**
* Get the batch operation for the given context.
* @param context The context
* @param The entity type
* @return The paged query
*/
@SuppressWarnings("unchecked")
protected InsertOperation getInsertOperation(@NonNull MethodInvocationContext context) {
E o = (E) getRequiredEntity(context);
return new DefaultInsertOperation<>(context, o);
}
/**
* Get the batch operation for the given context.
* @param context The context
* @param The entity type
* @return The paged query
*/
@SuppressWarnings("unchecked")
protected UpdateOperation getUpdateOperation(@NonNull MethodInvocationContext context) {
E o = (E) getRequiredEntity(context);
return new DefaultUpdateOperation<>(context, o);
}
/**
* Get the batch operation for the given context.
* @param context The context
* @param entity The entity
* @param The entity type
* @return The paged query
*/
protected InsertOperation getInsertOperation(@NonNull MethodInvocationContext context, E entity) {
return new DefaultInsertOperation<>(context, entity);
}
/**
* Validates null arguments ensuring no argument is null unless declared so.
* @param context The context
*/
protected final void validateNullArguments(MethodInvocationContext context) {
Object[] parameterValues = context.getParameterValues();
for (int i = 0; i < parameterValues.length; i++) {
Object o = parameterValues[i];
if (o == null && !context.getArguments()[i].isNullable()) {
throw new IllegalArgumentException("Argument [" + context.getArguments()[i].getName() + "] value is null and the method parameter is not declared as nullable");
}
}
}
/**
* Default implementation of {@link InsertOperation}.
* @param The entity type
*/
private final class DefaultInsertOperation implements InsertOperation {
private final MethodInvocationContext, ?> method;
private final E entity;
DefaultInsertOperation(MethodInvocationContext, ?> method, E entity) {
this.method = method;
this.entity = entity;
}
@NonNull
@Override
public Class getRootEntity() {
return (Class) entity.getClass();
}
@NonNull
@Override
public Class> getRepositoryType() {
return method.getTarget().getClass();
}
@Override
public E getEntity() {
return entity;
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return method.getAnnotationMetadata();
}
}
/**
* Default implementation of {@link UpdateOperation}.
* @param The entity type
*/
private final class DefaultUpdateOperation implements UpdateOperation {
private final MethodInvocationContext, ?> method;
private final E entity;
DefaultUpdateOperation(MethodInvocationContext, ?> method, E entity) {
this.method = method;
this.entity = entity;
}
@NonNull
@Override
public Class getRootEntity() {
return (Class) entity.getClass();
}
@NonNull
@Override
public Class> getRepositoryType() {
return method.getTarget().getClass();
}
@Override
public E getEntity() {
return entity;
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return method.getAnnotationMetadata();
}
}
/**
* Default implementation of {@link BatchOperation}.
* @param The entity type
*/
private final class DefaultBatchOperation implements BatchOperation {
private final MethodInvocationContext, ?> method;
private final @NonNull Class rootEntity;
private final Iterable iterable;
public DefaultBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
this.method = method;
this.rootEntity = rootEntity;
this.iterable = iterable;
}
@NonNull
@Override
public Class getRootEntity() {
return rootEntity;
}
@NonNull
@Override
public Class> getRepositoryType() {
return method.getTarget().getClass();
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
public Iterator iterator() {
return iterable.iterator();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return method.getAnnotationMetadata();
}
}
/**
* Default implementation of {@link BatchOperation}.
*
* @param The entity type
*/
private final class AllBatchOperation implements BatchOperation {
private final MethodInvocationContext, ?> method;
private final @NonNull Class rootEntity;
public AllBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity) {
this.method = method;
this.rootEntity = rootEntity;
}
@Override
public boolean all() {
return true;
}
@NonNull
@Override
public Class getRootEntity() {
return rootEntity;
}
@NonNull
@Override
public Class> getRepositoryType() {
return method.getTarget().getClass();
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
public Iterator iterator() {
return Collections.emptyIterator();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return method.getAnnotationMetadata();
}
}
/**
* Default implementation of {@link PagedQuery}.
*
* @param The paged query
*/
private final class DefaultPagedQuery implements PagedQuery {
private final ExecutableMethod, ?> method;
private final @NonNull Class rootEntity;
private final Pageable pageable;
/**
* Default constructor.
* @param method The method
* @param rootEntity The root entity
* @param pageable The pageable
*/
DefaultPagedQuery(ExecutableMethod, ?> method, @NonNull Class rootEntity, Pageable pageable) {
this.method = method;
this.rootEntity = rootEntity;
this.pageable = pageable;
}
@NonNull
@Override
public Class getRootEntity() {
return rootEntity;
}
@NonNull
@Override
public Pageable getPageable() {
return pageable;
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return method.getAnnotationMetadata();
}
}
/**
* Represents a prepared query.
*
* @param The entity type
* @param The result type
*/
private final class DefaultStoredQuery implements StoredQuery {
private final @NonNull Class resultType;
private final @NonNull Class rootEntity;
private final @NonNull String query;
private final @Nullable Map parameterBinding;
private final @Nullable int[] indexedParameterBinding;
private final @Nullable String[] parameterPaths;
private final ExecutableMethod, ?> method;
private final String lastUpdatedProp;
private final boolean isDto;
private final boolean isNative;
private final boolean isNumericPlaceHolder;
private final boolean hasPageable;
private final AnnotationMetadata annotationMetadata;
private final boolean hasIn;
private final boolean isCount;
private final DataType[] indexedDataTypes;
private final String[] parameterNames;
private final boolean hasResultConsumer;
private Map queryHints;
private Set joinFetchPaths = null;
/**
* The default constructor.
* @param method The target method
* @param resultType The result type of the query
* @param rootEntity The root entity of the query
* @param query The query itself
* @param isCount Is the query a count query
*/
DefaultStoredQuery(
@NonNull ExecutableMethod, ?> method,
@NonNull Class resultType,
@NonNull Class rootEntity,
@NonNull String query,
@Nullable String parameterBindingMember,
boolean isCount) {
this.resultType = ReflectionUtils.getWrapperType(resultType);
this.rootEntity = rootEntity;
this.annotationMetadata = method.getAnnotationMetadata();
this.isNative = method.isTrue(Query.class, "nativeQuery");
this.hasResultConsumer = method.stringValue(PREDATOR_ANN_NAME, "sqlMappingFunction").isPresent();
this.isNumericPlaceHolder = method
.classValue(RepositoryConfiguration.class, "queryBuilder")
.map(c -> c == SqlQueryBuilder.class).orElse(false);
this.hasIn = isNumericPlaceHolder && query.contains(SqlQueryBuilder.IN_EXPRESSION_START);
this.hasPageable = method.stringValue(PREDATOR_ANN_NAME, TypeRole.PAGEABLE).isPresent() ||
method.stringValue(PREDATOR_ANN_NAME, TypeRole.SORT).isPresent() ||
method.intValue(PREDATOR_ANN_NAME, META_MEMBER_PAGE_SIZE).orElse(-1) > -1;
if (isNumericPlaceHolder && method.isTrue(Query.class, DataMethod.META_MEMBER_RAW_QUERY)) {
Matcher matcher = VARIABLE_PATTERN.matcher(query);
this.query = matcher.replaceAll("?");
} else {
this.query = query;
}
this.method = method;
this.lastUpdatedProp = method.stringValue(PREDATOR_ANN_NAME, TypeRole.LAST_UPDATED_PROPERTY).orElse(null);
this.isDto = method.isTrue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_DTO);
this.isCount = isCount;
AnnotationValue annotation = annotationMetadata.getAnnotation(DataMethod.class);
if (parameterBindingMember != null && annotation != null) {
this.indexedParameterBinding = annotation.get(
parameterBindingMember, int[].class).orElse(EMPTY_INT_ARRAY);
if (isNumericPlaceHolder) {
String[] strArray = annotation.stringValues(parameterBindingMember + "Paths");
if (strArray.length == indexedParameterBinding.length) {
for (int i = 0; i < strArray.length; i++) {
String s = strArray[i];
if (StringUtils.isEmpty(s)) {
strArray[i] = null;
}
}
this.parameterPaths = strArray;
} else {
this.parameterPaths = new String[indexedParameterBinding.length];
}
this.parameterBinding = null;
this.parameterNames = null;
} else {
this.parameterBinding = null;
this.parameterPaths = annotation.stringValues(parameterBindingMember + "Paths");
this.parameterNames = annotation.stringValues(parameterBindingMember + "Names");
}
} else {
this.indexedParameterBinding = EMPTY_INT_ARRAY;
this.parameterPaths = null;
this.parameterBinding = null;
this.parameterNames = null;
}
if (method.hasAnnotation(QueryHint.class)) {
List> values = method.getAnnotationValuesByType(QueryHint.class);
this.queryHints = new HashMap<>(values.size());
for (AnnotationValue value : values) {
String n = value.stringValue("name").orElse(null);
String v = value.stringValue("value").orElse(null);
if (StringUtils.isNotEmpty(n) && StringUtils.isNotEmpty(v)) {
queryHints.put(n, v);
}
}
}
Map queryHints = operations.getQueryHints(this);
if (queryHints != Collections.EMPTY_MAP) {
if (this.queryHints != null) {
this.queryHints.putAll(queryHints);
} else {
this.queryHints = queryHints;
}
}
if (isNumericPlaceHolder) {
this.indexedDataTypes = annotationMetadata
.getValue(DataMethod.class, DataMethod.META_MEMBER_PARAMETER_TYPE_DEFS, DataType[].class)
.orElse(DataType.EMPTY_DATA_TYPE_ARRAY);
} else {
this.indexedDataTypes = null;
}
}
@Override
public String[] getParameterNames() {
if (parameterNames == null) {
return StringUtils.EMPTY_STRING_ARRAY;
}
return this.parameterNames;
}
@Override
public String[] getIndexedParameterPaths() {
if (parameterPaths != null) {
return parameterPaths;
}
return StringUtils.EMPTY_STRING_ARRAY;
}
@NonNull
@Override
public Set getJoinFetchPaths() {
if (joinFetchPaths == null) {
Set set = method.getAnnotationValuesByType(Join.class).stream().filter(
this::isJoinFetch
).map(av -> {
String path = av.stringValue().orElseThrow(() -> new IllegalStateException("Should not include annotations without a value definition"));
String alias = av.stringValue("alias").orElse(null);
// only the alias and path is needed, don't materialize the rest
return new JoinPath(path, new Association[0], Join.Type.DEFAULT, alias);
}).collect(Collectors.toSet());
this.joinFetchPaths = set.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(set);
}
return joinFetchPaths;
}
@Override
public boolean isSingleResult() {
return !isCount() && getJoinFetchPaths().isEmpty();
}
@Override
public boolean hasResultConsumer() {
return this.hasResultConsumer;
}
private boolean isJoinFetch(AnnotationValue av) {
if (!av.stringValue().isPresent()) {
return false;
}
Optional type = av.stringValue("type");
return !type.isPresent() || type.get().contains("FETCH");
}
@Override
public boolean isCount() {
return isCount;
}
@NonNull
@Override
public DataType[] getIndexedParameterTypes() {
if (indexedDataTypes == null) {
return DataType.EMPTY_DATA_TYPE_ARRAY;
}
return this.indexedDataTypes;
}
@NonNull
@Override
public int[] getIndexedParameterBinding() {
if (this.indexedParameterBinding != null) {
return this.indexedParameterBinding;
}
return EMPTY_INT_ARRAY;
}
@NonNull
@Override
public Map getQueryHints() {
if (queryHints != null) {
return queryHints;
}
return Collections.emptyMap();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return annotationMetadata;
}
@Override
public boolean isNative() {
return isNative;
}
/**
* Is this a raw SQL query.
* @return The raw sql query.
*/
@Override
public boolean useNumericPlaceholders() {
return isNumericPlaceHolder;
}
/**
* @return Whether the query is a DTO query
*/
@Override
public boolean isDtoProjection() {
return isDto;
}
/**
* @return The result type
*/
@Override
@NonNull
public Class getResultType() {
return resultType;
}
@NonNull
@Override
public DataType getResultDataType() {
if (isCount) {
return DataType.LONG;
}
return annotationMetadata.enumValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_RESULT_DATA_TYPE, DataType.class)
.orElse(DataType.OBJECT);
}
/**
* @return The ID type
*/
@SuppressWarnings("unchecked")
@Override
public Optional> getEntityIdentifierType() {
Optional o = annotationMetadata.classValue(PREDATOR_ANN_NAME, DataMethod.META_MEMBER_ID_TYPE);
return o;
}
/**
* @return The root entity type
*/
@Override
@NonNull
public Class getRootEntity() {
return rootEntity;
}
/**
* Does the query contain an in expression.
* @return True if it does
*/
@Override
public boolean hasInExpression() {
return hasIn;
}
@Override
public boolean hasPageable() {
return hasPageable;
}
/**
* @return The query to execute
*/
@Override
@NonNull
public String getQuery() {
return query;
}
@Nonnull
@Override
public String getName() {
return method.getMethodName();
}
@Override
@NonNull
public Class>[] getArgumentTypes() {
return method.getArgumentTypes();
}
@NonNull
@Override
public Map getParameterBinding() {
if (parameterBinding == null) {
return Collections.emptyMap();
}
return parameterBinding;
}
@Override
public String getLastUpdatedProperty() {
return lastUpdatedProp;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultStoredQuery, ?> that = (DefaultStoredQuery, ?>) o;
return resultType.equals(that.resultType) &&
method.equals(that.method);
}
@Override
public int hashCode() {
return Objects.hash(resultType, method);
}
}
/**
* Represents a prepared query.
*
* @param The entity type
* @param The result type
*/
private final class DefaultPreparedQuery implements PreparedQuery {
private final Pageable pageable;
private final StoredQuery storedQuery;
private final String query;
private final boolean dto;
private final MethodInvocationContext context;
/**
* The default constructor.
*
* @param context The execution context
* @param storedQuery The stored query
* @param finalQuery The final query
* @param pageable The pageable
* @param dtoProjection Whether the prepared query is a dto projection
*/
DefaultPreparedQuery(
MethodInvocationContext context,
StoredQuery storedQuery,
String finalQuery,
@NonNull Pageable pageable,
boolean dtoProjection) {
this.context = context;
this.query = finalQuery;
this.storedQuery = storedQuery;
this.pageable = pageable;
this.dto = dtoProjection;
}
@Override
public String[] getParameterNames() {
return storedQuery.getParameterNames();
}
@Override
public String[] getIndexedParameterPaths() {
return storedQuery.getIndexedParameterPaths();
}
@Override
public Optional getParameterInRole(@NonNull String role, @NonNull Class type) {
return AbstractQueryInterceptor.this.getParameterInRole(context, role, type);
}
@Override
public Class> getLastUpdatedType() {
return AbstractQueryInterceptor.this.getLastUpdatedType(getRootEntity(), getLastUpdatedProperty());
}
@Override
public boolean hasResultConsumer() {
return storedQuery.hasResultConsumer();
}
@NonNull
@Override
public int[] getIndexedParameterBinding() {
return storedQuery.getIndexedParameterBinding();
}
@NonNull
@Override
public Set getJoinFetchPaths() {
return storedQuery.getJoinFetchPaths();
}
@Override
public boolean isSingleResult() {
return storedQuery.isSingleResult();
}
@NonNull
@Override
public DataType[] getIndexedParameterTypes() {
return storedQuery.getIndexedParameterTypes();
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return storedQuery.getAnnotationMetadata();
}
@NonNull
@Override
public Map getQueryHints() {
return storedQuery.getQueryHints();
}
@Override
public Class> getRepositoryType() {
return context.getTarget().getClass();
}
@NonNull
@Override
public Map getParameterValues() {
return buildParameterValues(context, this);
}
@Override
public Object[] getParameterArray() {
return context.getParameterValues();
}
@Override
public Argument[] getArguments() {
return context.getArguments();
}
@NonNull
@Override
public Pageable getPageable() {
if (storedQuery.isCount()) {
return Pageable.UNPAGED;
} else {
return pageable;
}
}
@Override
public boolean isNative() {
return storedQuery.isNative();
}
@Override
public boolean useNumericPlaceholders() {
return storedQuery.useNumericPlaceholders();
}
@Override
public boolean isDtoProjection() {
return dto;
}
@NonNull
@Override
public Class getResultType() {
return storedQuery.getResultType();
}
@NonNull
@Override
public DataType getResultDataType() {
return storedQuery.getResultDataType();
}
@Nullable
@Override
public Optional> getEntityIdentifierType() {
return storedQuery.getEntityIdentifierType();
}
@NonNull
@Override
public Class getRootEntity() {
return storedQuery.getRootEntity();
}
@Override
public boolean hasInExpression() {
return storedQuery.hasInExpression();
}
@Override
public boolean hasPageable() {
return storedQuery.hasPageable();
}
@NonNull
@Override
public String getQuery() {
return query;
}
@NonNull
@Override
public Class>[] getArgumentTypes() {
return storedQuery.getArgumentTypes();
}
@NonNull
@Override
public Map getParameterBinding() {
return storedQuery.getParameterBinding();
}
@Override
public boolean isCount() {
return storedQuery.isCount();
}
@Override
public String getLastUpdatedProperty() {
return storedQuery.getLastUpdatedProperty();
}
@Nonnull
@Override
public String getName() {
return storedQuery.getName();
}
}
}