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 io.micronaut.aop.InvocationContext;
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.annotation.Experimental;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.beans.BeanWrapper;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
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.data.annotation.Query;
import io.micronaut.data.annotation.TypeRole;
import io.micronaut.data.exceptions.EmptyResultException;
import io.micronaut.data.intercept.DataInterceptor;
import io.micronaut.data.intercept.RepositoryMethodKey;
import io.micronaut.data.intercept.annotation.DataMethod;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.PersistentEntity;
import io.micronaut.data.model.PersistentProperty;
import io.micronaut.data.model.Sort;
import io.micronaut.data.model.runtime.AbstractPreparedDataOperation;
import io.micronaut.data.model.runtime.BatchOperation;
import io.micronaut.data.model.runtime.DefaultStoredDataOperation;
import io.micronaut.data.model.runtime.DeleteBatchOperation;
import io.micronaut.data.model.runtime.DeleteOperation;
import io.micronaut.data.model.runtime.DeleteReturningBatchOperation;
import io.micronaut.data.model.runtime.DeleteReturningOperation;
import io.micronaut.data.model.runtime.EntityInstanceOperation;
import io.micronaut.data.model.runtime.EntityOperation;
import io.micronaut.data.model.runtime.InsertBatchOperation;
import io.micronaut.data.model.runtime.InsertOperation;
import io.micronaut.data.model.runtime.PagedQuery;
import io.micronaut.data.model.runtime.PreparedQuery;
import io.micronaut.data.model.runtime.StoredQuery;
import io.micronaut.data.model.runtime.UpdateBatchOperation;
import io.micronaut.data.model.runtime.UpdateOperation;
import io.micronaut.data.operations.HintsCapableRepository;
import io.micronaut.data.operations.RepositoryOperations;
import io.micronaut.data.runtime.query.DefaultPagedQueryResolver;
import io.micronaut.data.runtime.query.DefaultPreparedQueryResolver;
import io.micronaut.data.runtime.query.DefaultStoredQueryResolver;
import io.micronaut.data.runtime.query.MethodContextAwareStoredQueryDecorator;
import io.micronaut.data.runtime.query.PagedQueryResolver;
import io.micronaut.data.runtime.query.PreparedQueryDecorator;
import io.micronaut.data.runtime.query.PreparedQueryResolver;
import io.micronaut.data.runtime.query.StoredQueryDecorator;
import io.micronaut.data.runtime.query.StoredQueryResolver;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_LIMIT;
import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_OFFSET;
import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_PAGE_SIZE;
/**
* Abstract interceptor that executes a {@link Query}.
*
* @param The declaring type
* @param The return type
* @author graemerocher
* @since 1.0
*/
public abstract class AbstractQueryInterceptor implements DataInterceptor {
protected final ConversionService conversionService;
protected final RepositoryOperations operations;
protected final PreparedQueryResolver preparedQueryResolver;
private final ConcurrentMap countQueries = new ConcurrentHashMap<>(50);
private final ConcurrentMap queries = new ConcurrentHashMap<>(50);
private final StoredQueryResolver storedQueryResolver;
private final MethodContextAwareStoredQueryDecorator storedQueryDecorator;
private final PagedQueryResolver pagedQueryResolver;
private final PreparedQueryDecorator preparedQueryDecorator;
/**
* Default constructor.
*
* @param operations The operations
*/
protected AbstractQueryInterceptor(@NonNull RepositoryOperations operations) {
ArgumentUtils.requireNonNull("operations", operations);
this.conversionService = operations.getConversionService();
this.operations = operations;
this.storedQueryResolver = operations instanceof StoredQueryResolver sQueryResolver ? sQueryResolver : new DefaultStoredQueryResolver() {
@Override
protected HintsCapableRepository getHintsCapableRepository() {
return operations;
}
};
if (operations instanceof MethodContextAwareStoredQueryDecorator methodDecorator) {
storedQueryDecorator = methodDecorator;
} else if (operations instanceof StoredQueryDecorator decorator) {
storedQueryDecorator = new MethodContextAwareStoredQueryDecorator() {
@Override
public StoredQuery decorate(MethodInvocationContext, ?> context, StoredQuery storedQuery) {
return decorator.decorate(storedQuery);
}
};
} else {
storedQueryDecorator = new MethodContextAwareStoredQueryDecorator() {
@Override
public StoredQuery decorate(MethodInvocationContext, ?> context, StoredQuery storedQuery) {
return storedQuery;
}
};
}
this.preparedQueryResolver = operations instanceof PreparedQueryResolver resolver ? resolver : new DefaultPreparedQueryResolver() {
@Override
protected ConversionService getConversionService() {
return operations.getConversionService();
}
};
this.preparedQueryDecorator = operations instanceof PreparedQueryDecorator decorator ? decorator : new PreparedQueryDecorator() {
@Override
public PreparedQuery decorate(PreparedQuery preparedQuery) {
return preparedQuery;
}
};
this.pagedQueryResolver = operations instanceof PagedQueryResolver resolver ? resolver : new DefaultPagedQueryResolver();
}
/**
* Returns parameter values with respect of {@link Parameter} annotation.
*
* @param context The method invocation context
* @return The parameters value map
*/
@NonNull
protected Map getParameterValueMap(MethodInvocationContext, ?> context) {
Argument>[] arguments = context.getArguments();
Object[] parameterValues = context.getParameterValues();
Map valueMap = new LinkedHashMap<>(arguments.length);
for (int i = 0; i < parameterValues.length; i++) {
Object parameterValue = parameterValues[i];
Argument arg = arguments[i];
valueMap.put(arg.getAnnotationMetadata().stringValue(Parameter.class).orElseGet(arg::getName), parameterValue);
}
return valueMap;
}
/**
* Returns the return type.
*
* @param context The context
* @return the return type
*/
protected Argument> getReturnType(MethodInvocationContext, ?> context) {
return context.getReturnType().asArgument();
}
@Nullable
protected final Object convertOne(MethodInvocationContext, ?> context, @Nullable Object o) {
Argument> argumentType = getReturnType(context);
Class> type = argumentType.getType();
if (o == null) {
if (type == Optional.class) {
return Optional.empty();
}
if (argumentType.isDeclaredNonNull() || !argumentType.isNullable()
&& !context.getReturnType().asArgument().isNullable()) {
throw new EmptyResultException();
}
return null;
}
boolean isOptional = false;
if (type == Optional.class) {
argumentType = argumentType.getFirstTypeVariable().orElse(Argument.OBJECT_ARGUMENT);
isOptional = true;
}
o = convertOne(o, argumentType);
if (isOptional) {
return Optional.of(o);
}
return o;
}
protected final Object convertOne(Object o, Argument> argumentType) {
if (argumentType.isInstance(o)) {
return o;
}
return operations.getConversionService().convertRequired(o, argumentType);
}
/**
* Prepares a query for the given context.
*
* @param The result generic type
* @param methodKey The method key
* @param context The context
* @return The query
*/
@NonNull
protected final PreparedQuery, RT> prepareQuery(RepositoryMethodKey methodKey,
MethodInvocationContext context) {
validateNullArguments(context);
StoredQuery, RT> storedQuery = findStoreQuery(methodKey, context);
Pageable pageable = storedQuery.hasPageable() ? getPageable(context) : Pageable.UNPAGED;
PreparedQuery, RT> preparedQuery = preparedQueryResolver.resolveQuery(context, storedQuery, pageable);
return preparedQueryDecorator.decorate(preparedQuery);
}
private StoredQuery findStoreQuery(MethodInvocationContext, ?> context) {
RepositoryMethodKey key = new RepositoryMethodKey(context.getTarget(), context.getExecutableMethod());
return findStoreQuery(key, context);
}
private StoredQuery findStoreQuery(RepositoryMethodKey methodKey, MethodInvocationContext, ?> context) {
StoredQuery storedQuery = queries.get(methodKey);
if (storedQuery == null) {
storedQuery = storedQueryResolver.resolveQuery(context);
storedQuery = storedQueryDecorator.decorate(context, storedQuery);
queries.put(methodKey, storedQuery);
}
return storedQuery;
}
/**
* Prepares a query for the given context.
*
* @param methodKey The method key
* @param context The context
* @return The query
*/
@NonNull
protected final PreparedQuery, Number> prepareCountQuery(RepositoryMethodKey methodKey, @NonNull MethodInvocationContext context) {
StoredQuery storedQuery = countQueries.get(methodKey);
if (storedQuery == null) {
storedQuery = storedQueryResolver.resolveCountQuery(context);
storedQuery = storedQueryDecorator.decorate(context, storedQuery);
countQueries.put(methodKey, storedQuery);
}
Pageable pageable = storedQuery.hasPageable() ? getPageable(context) : Pageable.UNPAGED;
//noinspection unchecked
PreparedQuery, Number> preparedQuery = preparedQueryResolver.resolveCountQuery(context, storedQuery, pageable);
return preparedQueryDecorator.decorate(preparedQuery);
}
/**
* Obtains the root entity or throws an exception if it is not available.
*
* @param context The context
* @param The entity type
* @return The root entity type
* @throws IllegalStateException If the root entity is unavailable
*/
@NonNull
protected Class getRequiredRootEntity(MethodInvocationContext context) {
Class aClass = context.classValue(DataMethod.NAME, DataMethod.META_MEMBER_ROOT_ENTITY).orElse(null);
if (aClass != null) {
return aClass;
} else {
final AnnotationValue ann = context.getDeclaredAnnotation(DataMethod.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 an entity parameter value in role.
*
* @param context The context
* @param type The type
* @param The generic type
* @return An result
*/
protected RT getEntityParameter(MethodInvocationContext, ?> context, @NonNull Class type) {
return getRequiredParameterInRole(context, TypeRole.ENTITY, type);
}
/**
* Retrieve an entities parameter value in role.
*
* @param context The context
* @param type The type
* @param The generic type
* @return An result
*/
protected Iterable getEntitiesParameter(MethodInvocationContext, ?> context, @NonNull Class type) {
return getRequiredParameterInRole(context, TypeRole.ENTITIES, Iterable.class);
}
/**
* Find an entity parameter value in role.
*
* @param context The context
* @param type The type
* @param The generic type
* @return An result
*/
protected Optional findEntityParameter(MethodInvocationContext, ?> context, @NonNull Class type) {
return getParameterInRole(context, TypeRole.ENTITY, type);
}
/**
* Fid an entities parameter value in role.
*
* @param context The context
* @param type The type
* @param The generic type
* @return An result
*/
protected Optional> findEntitiesParameter(MethodInvocationContext, ?> context, @NonNull Class type) {
Optional parameterInRole = getParameterInRole(context, TypeRole.ENTITIES, Iterable.class);
return (Optional>) parameterInRole;
}
/**
* 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 result
*/
protected RT getRequiredParameterInRole(MethodInvocationContext, ?> context, @NonNull String role, @NonNull Class type) {
return getParameterInRole(context, role, type).orElseThrow(() -> new IllegalStateException("Cannot find parameter with role: " + role));
}
/**
* 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
*/
protected Optional getParameterInRole(MethodInvocationContext, ?> context, @NonNull String role, @NonNull Class type) {
return context.stringValue(DataMethod.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 = operations.getConversionService()
.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 = getPageableInRole(context);
if (pageable == null) {
pageable = Pageable.UNPAGED;
int limit = context.intValue(DataMethod.NAME, META_MEMBER_PAGE_SIZE)
.orElseGet(() -> context.intValue(DataMethod.NAME, META_MEMBER_LIMIT).orElse(-1));
if (limit > 0) {
pageable = Pageable.from(0, limit);
}
Sort sort = getParameterInRole(context, TypeRole.SORT, Sort.class).orElse(null);
if (sort != null) {
return pageable.orders(sort.getOrderBy());
}
}
return pageable;
}
/**
* Resolves the {@link Pageable} for the given context.
*
* @param context The context
* @return The pageable or null
*/
@Nullable
protected Pageable getPageableInRole(MethodInvocationContext, ?> context) {
return getParameterInRole(context, TypeRole.PAGEABLE, Pageable.class).orElse(null);
}
/**
* Resolves the offset.
*
* @param context The context
* @return The offset or -1
* @since 4.10
*/
protected int getOffset(MethodInvocationContext, ?> context) {
return context.intValue(DataMethod.class, META_MEMBER_OFFSET).orElse(-1);
}
/**
* Resolves the limit.
*
* @param context The context
* @return The limit or -1
* @since 4.10
*/
protected int getLimit(MethodInvocationContext, ?> context) {
return context.intValue(DataMethod.class, META_MEMBER_LIMIT).orElse(-1);
}
/**
* Return whether the metadata indicates the instance is nullable.
*
* @param metadata The metadata
* @return True if it is nullable
* @deprecated Not used
*/
@Deprecated(forRemoval = true, since = "4.10")
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
*/
@NonNull
protected Object getRequiredEntity(MethodInvocationContext context) {
String entityParam = context.stringValue(DataMethod.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;
}
/**
* 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
*/
@NonNull
protected 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];
boolean strictNullable = true;
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");
} else {
// If Optional or AutoGenerated or AutoPopulated with null value
// is one of fields, then we shouldn't force strict nullability check
strictNullable = false;
}
}
arguments[i] = v;
}
instance = introspection.instantiate(strictNullable, arguments);
} else {
instance = introspection.instantiate();
}
BeanWrapper wrapper = BeanWrapper.getWrapper(instance);
Collection extends PersistentProperty> persistentProperties = entity.getPersistentProperties();
PersistentProperty identity = entity.getIdentity();
if (identity != null) {
setProperty(wrapper, identity, parameterValues);
} else {
PersistentProperty[] compositeIdentities = entity.getCompositeIdentity();
if (compositeIdentities != null && compositeIdentities.length > 0) {
for (PersistentProperty compositeIdentity : compositeIdentities) {
setProperty(wrapper, compositeIdentity, parameterValues);
}
}
}
for (PersistentProperty prop : persistentProperties) {
setProperty(wrapper, prop, parameterValues);
}
return instance;
}
/**
* Get the paged query for the given context.
*
* @param context The context
* @param The entity type
* @return The paged query
*/
@NonNull
protected PagedQuery getPagedQuery(@NonNull MethodInvocationContext context) {
return pagedQueryResolver.resolveQuery(context, getRequiredRootEntity(context), getPageable(context));
}
/**
* Get the insert batch operation for the given context.
*
* @param context The context
* @param iterable The iterable
* @param The entity type
* @return The paged query
*/
@NonNull
protected InsertBatchOperation getInsertBatchOperation(@NonNull MethodInvocationContext context, @NonNull Iterable iterable) {
@SuppressWarnings("unchecked") Class rootEntity = getRequiredRootEntity(context);
return getInsertBatchOperation(context, rootEntity, iterable);
}
/**
* Get the insert 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
*/
@NonNull
protected InsertBatchOperation getInsertBatchOperation(@NonNull MethodInvocationContext context, Class rootEntity, @NonNull Iterable iterable) {
return new DefaultInsertBatchOperation<>(context, rootEntity, iterable);
}
/**
* Get the batch operation for the given context.
*
* @param context The context
* @param The entity type
* @return The paged query
*/
@SuppressWarnings("unchecked")
@NonNull
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")
@NonNull
protected UpdateOperation getUpdateOperation(@NonNull MethodInvocationContext context) {
return getUpdateOperation(context, (E) getRequiredEntity(context));
}
/**
* Get the batch operation for the given context.
*
* @param context The context
* @param entity The entity instance
* @param The entity type
* @return The paged query
*/
@SuppressWarnings("unchecked")
@NonNull
protected UpdateOperation getUpdateOperation(@NonNull MethodInvocationContext context, E entity) {
return new DefaultUpdateOperation<>(context, entity);
}
/**
* Get the update all batch operation for the given context.
*
* @param The entity type
* @param rootEntity The root entitry
* @param context The context
* @param iterable The iterable
* @return The paged query
*/
@NonNull
protected UpdateBatchOperation getUpdateAllBatchOperation(@NonNull MethodInvocationContext context, Class rootEntity, @NonNull Iterable iterable) {
return new DefaultUpdateBatchOperation<>(context, rootEntity, iterable);
}
/**
* Get the delete operation for the given context.
*
* @param context The context
* @param entity The entity
* @param The entity type
* @return The paged query
*/
@NonNull
protected DeleteOperation getDeleteOperation(@NonNull MethodInvocationContext context, @NonNull E entity) {
return new DefaultDeleteOperation<>(context, entity);
}
/**
* Get the delete operation for the given context.
*
* @param context The context
* @param entity The entity
* @param The entity type
* @param The result type
* @return The paged query
*/
@NonNull
@Experimental
protected DeleteReturningOperation getDeleteReturningOperation(@NonNull MethodInvocationContext context, @NonNull E entity) {
return new DefaultDeleteReturningOperation<>(context, entity);
}
/**
* Get the delete all batch operation for the given context.
*
* @param context The context
* @param The entity type
* @return The paged query
*/
@NonNull
protected DeleteBatchOperation getDeleteAllBatchOperation(@NonNull MethodInvocationContext context) {
Class rootEntity = getRequiredRootEntity(context);
return new DefaultDeleteAllBatchOperation<>(context, rootEntity);
}
/**
* Get the delete batch operation for the given context.
*
* @param context The context
* @param iterable The iterable
* @param The entity type
* @return The paged query
*/
@NonNull
protected DeleteBatchOperation getDeleteBatchOperation(@NonNull MethodInvocationContext context, @NonNull Iterable iterable) {
Class rootEntity = getRequiredRootEntity(context);
return getDeleteBatchOperation(context, rootEntity, iterable);
}
/**
* Get the delete returning batch operation for the given context.
*
* @param context The context
* @param iterable The iterable
* @param The entity type
* @param The result type
* @return The paged query
*/
@Experimental
@NonNull
protected DeleteReturningBatchOperation getDeleteReturningBatchOperation(@NonNull MethodInvocationContext context, @NonNull Iterable iterable) {
Class rootEntity = getRequiredRootEntity(context);
return new DefaultDeleteReturningBatchOperation<>(context, rootEntity, iterable);
}
/**
* Get the delete 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
*/
@NonNull
protected DeleteBatchOperation getDeleteBatchOperation(@NonNull MethodInvocationContext context, Class rootEntity, @NonNull Iterable iterable) {
return new DefaultDeleteBatchOperation<>(context, rootEntity, iterable);
}
/**
* Get the batch operation for the given context.
*
* @param context The context
* @param entity The entity
* @param The entity type
* @return The paged query
*/
@NonNull
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");
}
}
}
/**
* Count the items.
*
* @param iterable the iterable
* @return the size
*/
protected int count(Iterable> iterable) {
if (iterable instanceof Collection> collection) {
return collection.size();
}
Iterator> iterator = iterable.iterator();
int i = 0;
while (iterator.hasNext()) {
i++;
iterator.next();
}
return i;
}
/**
* Is the type a number.
*
* @param type The type
* @return True if is a number
*/
protected boolean isNumber(@Nullable Class> type) {
if (type == null) {
return false;
}
if (type.isPrimitive()) {
return ClassUtils.getPrimitiveType(type.getName()).map(aClass ->
Number.class.isAssignableFrom(ReflectionUtils.getWrapperType(aClass))
).orElse(false);
}
return Number.class.isAssignableFrom(type);
}
/**
* Sets the property value for given persistent property of the {@link BeanWrapper} if property
* present in given parameter values and property not readonly or generated.
*
* @param wrapper the bean wrapper
* @param prop the persistent property
* @param parameterValues the parameter value map
*/
private static void setProperty(BeanWrapper wrapper, PersistentProperty prop, Map parameterValues) {
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");
}
}
}
}
/**
* Default implementation of {@link InsertOperation}.
*
* @param The entity type
*/
private final class DefaultInsertOperation extends AbstractEntityOperation implements InsertOperation {
private final E entity;
DefaultInsertOperation(MethodInvocationContext, ?> method, E entity) {
super(method, (Class) entity.getClass());
this.entity = entity;
}
@Override
public E getEntity() {
return entity;
}
}
/**
* Default implementation of {@link DeleteOperation}.
*
* @param The entity type
*/
private final class DefaultDeleteOperation extends AbstractEntityInstanceOperation implements DeleteOperation {
DefaultDeleteOperation(MethodInvocationContext, ?> method, E entity) {
super(method, entity);
}
}
/**
* Default implementation of {@link DeleteReturningOperation}.
*
* @param The entity type
* @param The result type
*/
private final class DefaultDeleteReturningOperation extends AbstractEntityInstanceOperation implements DeleteReturningOperation {
DefaultDeleteReturningOperation(MethodInvocationContext, ?> method, E entity) {
super(method, entity);
}
@Override
public StoredQuery getStoredQuery() {
return (StoredQuery) super.getStoredQuery();
}
}
/**
* Default implementation of {@link DeleteReturningBatchOperation}.
*
* @param The entity type
* @param The result type
*/
private final class DefaultDeleteReturningBatchOperation extends DefaultDeleteBatchOperation implements DeleteReturningBatchOperation {
DefaultDeleteReturningBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
super(method, rootEntity, iterable);
}
@Override
public StoredQuery getStoredQuery() {
return (StoredQuery) super.getStoredQuery();
}
}
/**
* Default implementation of {@link UpdateOperation}.
*
* @param The entity type
*/
private final class DefaultUpdateOperation extends AbstractEntityOperation implements UpdateOperation {
private final E entity;
DefaultUpdateOperation(MethodInvocationContext, ?> method, E entity) {
super(method, (Class) entity.getClass());
this.entity = entity;
}
@Override
public E getEntity() {
return entity;
}
}
private abstract sealed class AbstractEntityInstanceOperation extends AbstractEntityOperation implements EntityInstanceOperation {
private final E entity;
AbstractEntityInstanceOperation(MethodInvocationContext, ?> method, E entity) {
super(method, (Class) entity.getClass());
this.entity = entity;
}
@NonNull
@Override
public E getEntity() {
return entity;
}
}
private abstract sealed class AbstractEntityOperation extends AbstractPreparedDataOperation implements EntityOperation {
protected final MethodInvocationContext, ?> method;
protected final Class rootEntity;
protected StoredQuery storedQuery;
AbstractEntityOperation(MethodInvocationContext, ?> method, Class rootEntity) {
super((MethodInvocationContext, E>) method, new DefaultStoredDataOperation<>(method.getExecutableMethod()));
this.method = method;
this.rootEntity = rootEntity;
}
@Override
public StoredQuery getStoredQuery() {
if (storedQuery == null) {
String queryString = method.stringValue(Query.class).orElse(null);
if (queryString == null) {
return null;
}
storedQuery = findStoreQuery(method);
}
return storedQuery;
}
@Override
public Optional getParameterInRole(@NonNull String role, @NonNull Class type) {
return AbstractQueryInterceptor.this.getParameterInRole(method, role, type);
}
@NonNull
@Override
public Class getRootEntity() {
return rootEntity;
}
@NonNull
@Override
public Class> getRepositoryType() {
return method.getTarget().getClass();
}
@NonNull
@Override
public String getName() {
return method.getDeclaringType().getSimpleName() + "." + method.getMethodName();
}
@Override
public InvocationContext, ?> getInvocationContext() {
return method;
}
}
/**
* Default implementation of {@link InsertBatchOperation}.
*
* @param The entity type
*/
private final class DefaultInsertBatchOperation extends DefaultBatchOperation implements InsertBatchOperation {
DefaultInsertBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
super(method, rootEntity, iterable);
}
@Override
public List> split() {
List> inserts = new ArrayList<>(10);
for (E e : iterable) {
inserts.add(new DefaultInsertOperation<>(method, e));
}
return inserts;
}
}
/**
* Default implementation of {@link DeleteBatchOperation}.
*
* @param The entity type
*/
private final class DefaultDeleteAllBatchOperation extends DefaultBatchOperation implements DeleteBatchOperation {
DefaultDeleteAllBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity) {
super(method, rootEntity, Collections.emptyList());
}
@Override
public boolean all() {
return true;
}
@Override
public List> split() {
throw new IllegalStateException("Split is not supported for delete all operation!");
}
}
/**
* Default implementation of {@link DeleteBatchOperation}.
*
* @param The entity type
*/
private sealed class DefaultDeleteBatchOperation extends DefaultBatchOperation implements DeleteBatchOperation {
DefaultDeleteBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
super(method, rootEntity, iterable);
}
@Override
public List> split() {
List> deletes = new ArrayList<>(10);
for (E e : iterable) {
deletes.add(new DefaultDeleteOperation<>(method, e));
}
return deletes;
}
}
/**
* Default implementation of {@link UpdateBatchOperation}.
*
* @param The entity type
*/
private final class DefaultUpdateBatchOperation extends DefaultBatchOperation implements UpdateBatchOperation {
DefaultUpdateBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
super(method, rootEntity, iterable);
}
@Override
public List> split() {
List> updates = new ArrayList<>(10);
for (E e : iterable) {
updates.add(new DefaultUpdateOperation<>(method, e));
}
return updates;
}
}
/**
* Default implementation of {@link BatchOperation}.
*
* @param The entity type
*/
private sealed class DefaultBatchOperation extends AbstractEntityOperation implements BatchOperation {
protected final Iterable iterable;
public DefaultBatchOperation(MethodInvocationContext, ?> method, @NonNull Class rootEntity, Iterable iterable) {
super(method, rootEntity);
this.iterable = iterable;
}
@Override
public Iterator iterator() {
return iterable.iterator();
}
}
}