DATACMNS-1260 - Extract EvaluationContextProvider and corresponding SPIs into dedicated package.
We now have a refined replica of the EvaluationContextProvider API and SPIs in the org.springframework.data.spel package. It has seen a bit of a Java 8 overhaul by removing the SPI support class in favor of turning most methods in EvaluationContextExtension into default ones. The already existing API has been renamed to QueryMethodEvaluationContextProvider to indicate it's working with additional semantics specific to query methods (i.e. the Parameters metadata). The internals have been refactored to use the new API but still detect implementations of the old EvaluationContextExtension interface. The implementations get wrapped into an adapting proxy to satisfy the new API so that the actual inspection and usage of the extension is now already done using the new APIs. The repository configuration has slightly change so that the creation of the EvaluationContextProvider is now taking place within RepositoryFactoryBeanSupport's implementation of BeanFactoryAware. AbstractMappingContext is now ApplicationContextAware and holds an ExtensionAwareEvaluationContextProvider using the configured ApplicationContext. That EvaluationContextProvider is forwarded to all MutablePersistentEntity instances. BasicPersistentEntity now exposes getEvaluationContext(…) to subclasses to easily create an EvaluationContext using the extension aware infrastructure. Removed DefaultEvaluationContextProvider in favor of a simple constant in QueryMethodEvaluationContextProvider. Related tickets: DATACMNS-1258, DATACMNS-1108.
This commit is contained in:
@@ -37,6 +37,8 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
@@ -50,6 +52,8 @@ import org.springframework.data.mapping.model.MutablePersistentEntity;
|
||||
import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
|
||||
import org.springframework.data.mapping.model.Property;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.Streamable;
|
||||
@@ -79,7 +83,7 @@ import org.springframework.util.ReflectionUtils.FieldFilter;
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
|
||||
implements MappingContext<E, P>, ApplicationEventPublisherAware, InitializingBean {
|
||||
implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean {
|
||||
|
||||
private final Optional<E> NONE = Optional.empty();
|
||||
private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
|
||||
@@ -87,6 +91,7 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
|
||||
|
||||
private @Nullable ApplicationEventPublisher applicationEventPublisher;
|
||||
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
|
||||
|
||||
private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
|
||||
private boolean strict = false;
|
||||
@@ -109,6 +114,20 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
|
||||
|
||||
if (applicationEventPublisher == null) {
|
||||
this.applicationEventPublisher = applicationContext;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Set} of types to populate the context initially.
|
||||
*
|
||||
@@ -346,6 +365,8 @@ public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?
|
||||
|
||||
entity = createPersistentEntity(typeInformation);
|
||||
|
||||
entity.setEvaluationContextProvider(evaluationContextProvider);
|
||||
|
||||
// Eagerly cache the entity as we might have to find it during recursive lookups.
|
||||
persistentEntities.put(typeInformation, Optional.of(entity));
|
||||
|
||||
|
||||
@@ -47,8 +47,10 @@ import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -84,6 +86,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
private @Nullable P idProperty;
|
||||
private @Nullable P versionProperty;
|
||||
private PersistentPropertyAccessorFactory propertyAccessorFactory;
|
||||
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
|
||||
|
||||
private final Lazy<Alias> typeAlias;
|
||||
|
||||
@@ -240,6 +243,15 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.MutablePersistentEntity#setEvaluationContextProvider(org.springframework.data.spel.EvaluationContextProvider)
|
||||
*/
|
||||
@Override
|
||||
public void setEvaluationContextProvider(EvaluationContextProvider provider) {
|
||||
this.evaluationContextProvider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given property if it is a better candidate for the id property than the current id property.
|
||||
*
|
||||
@@ -464,11 +476,19 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
|
||||
return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<P> iterator() {
|
||||
return Collections.unmodifiableList(properties).iterator();
|
||||
}
|
||||
|
||||
protected EvaluationContext getEvaluationContext(Object rootObject) {
|
||||
return evaluationContextProvider.getEvaluationContext(rootObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the {@link Alias} to be used for the given type.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.spel.EvaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Interface capturing mutator methods for {@link PersistentEntity}s.
|
||||
@@ -58,4 +59,11 @@ public interface MutablePersistentEntity<T, P extends PersistentProperty<P>> ext
|
||||
* @param factory must not be {@literal null}.
|
||||
*/
|
||||
void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory);
|
||||
|
||||
/**
|
||||
* Sets the {@link EvaluationContextProvider} to be used by the entity.
|
||||
*
|
||||
* @param provider must not be {@literal null}.
|
||||
*/
|
||||
void setEvaluationContextProvider(EvaluationContextProvider provider);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.data.repository.config.RepositoryFragmentConfiguratio
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -53,7 +54,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Dirk Mahler
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @author Mark Paluchs
|
||||
* @author Peter Rietzler
|
||||
* @author Jens Schauder
|
||||
* @author Christoph Strobl
|
||||
@@ -392,14 +393,13 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
|
||||
|
||||
return fragmentConfigurations.flatMap(it -> {
|
||||
|
||||
Class<Object> interfaceClass = (Class) lookupFragmentInterface(repositoryType, it.getInterfaceName());
|
||||
Class<Object> interfaceClass = (Class<Object>) lookupFragmentInterface(repositoryType, it.getInterfaceName());
|
||||
Class<?> implementationClass = context.loadClass(it.getClassName());
|
||||
Optional<Bean<?>> bean = getBean(implementationClass, beanManager, qualifiers);
|
||||
|
||||
return bean.map(this::getDependencyInstance) //
|
||||
.map(implementation -> RepositoryFragment.implemented(interfaceClass, implementation)) //
|
||||
.map(Stream::of) //
|
||||
.orElse(Stream.empty());
|
||||
return Optionals.toStream(bean.map(this::getDependencyInstance) //
|
||||
.map(implementation -> RepositoryFragment.implemented(interfaceClass, implementation))); //
|
||||
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ package org.springframework.data.repository.cdi;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
|
||||
/**
|
||||
* Interface containing the configurable options for the Spring Data repository subsystem using CDI.
|
||||
@@ -31,13 +31,13 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
public interface CdiRepositoryConfiguration {
|
||||
|
||||
/**
|
||||
* Return the {@link EvaluationContextProvider} to use. Can be {@link Optional#empty()} .
|
||||
* Return the {@link QueryMethodEvaluationContextProvider} to use. Can be {@link Optional#empty()} .
|
||||
*
|
||||
* @return the optional {@link EvaluationContextProvider} base to use, can be {@link Optional#empty()}, must not be
|
||||
* {@literal null}.
|
||||
* @return the optional {@link QueryMethodEvaluationContextProvider} base to use, can be {@link Optional#empty()},
|
||||
* must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
default Optional<EvaluationContextProvider> getEvaluationContextProvider() {
|
||||
default Optional<QueryMethodEvaluationContextProvider> getEvaluationContextProvider() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import org.springframework.data.repository.config.RepositoryFragmentConfiguratio
|
||||
import org.springframework.data.repository.config.RepositoryFragmentDiscovery;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -159,7 +160,7 @@ public class CdiRepositoryContext {
|
||||
Collections.emptySet(), //
|
||||
BeanDefinition::getBeanClassName);
|
||||
|
||||
return beanDefinition.map(it -> loadClass(it.getBeanClassName()));
|
||||
return beanDefinition.map(this::loadBeanClass);
|
||||
}
|
||||
|
||||
private Optional<RepositoryFragmentConfiguration> detectRepositoryFragmentConfiguration(
|
||||
@@ -173,6 +174,14 @@ public class CdiRepositoryContext {
|
||||
return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(configuration.getFragmentInterfaceName(), bd));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Class<?> loadBeanClass(AbstractBeanDefinition definition) {
|
||||
|
||||
String beanClassName = definition.getBeanClassName();
|
||||
|
||||
return beanClassName == null ? null : loadClass(beanClassName);
|
||||
}
|
||||
|
||||
private static ClassMetadata getClassMetadata(MetadataReaderFactory metadataReaderFactory, String className) {
|
||||
|
||||
try {
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.core.type.ClassMetadata;
|
||||
@@ -40,7 +39,6 @@ import org.springframework.core.type.filter.TypeFilter;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragment;
|
||||
import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean;
|
||||
import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -134,12 +132,6 @@ class RepositoryBeanDefinitionBuilder {
|
||||
builder.addPropertyValue("repositoryFragments",
|
||||
ParsingUtils.getSourceBeanDefinition(fragmentsBuilder, configuration.getSource()));
|
||||
|
||||
RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
|
||||
ExtensionAwareEvaluationContextProvider.class);
|
||||
evaluationContextProviderDefinition.setSource(configuration.getSource());
|
||||
|
||||
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
@@ -36,11 +37,11 @@ import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -69,7 +70,7 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
private ClassLoader classLoader;
|
||||
private BeanFactory beanFactory;
|
||||
private boolean lazyInit = false;
|
||||
private EvaluationContextProvider evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
|
||||
private Optional<QueryMethodEvaluationContextProvider> evaluationContextProvider = Optional.empty();
|
||||
private ApplicationEventPublisher publisher;
|
||||
|
||||
private Lazy<T> repository;
|
||||
@@ -145,14 +146,13 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link EvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined queries.
|
||||
* Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined
|
||||
* queries.
|
||||
*
|
||||
* @param evaluationContextProvider can be {@literal null}, defaults to
|
||||
* {@link DefaultEvaluationContextProvider#INSTANCE}.
|
||||
* @param evaluationContextProvider must not be {@literal null}.
|
||||
*/
|
||||
public void setEvaluationContextProvider(EvaluationContextProvider evaluationContextProvider) {
|
||||
this.evaluationContextProvider = evaluationContextProvider == null ? DefaultEvaluationContextProvider.INSTANCE
|
||||
: evaluationContextProvider;
|
||||
public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this.evaluationContextProvider = Optional.of(evaluationContextProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +179,13 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
*/
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
|
||||
|
||||
this.beanFactory = beanFactory;
|
||||
|
||||
if (!this.evaluationContextProvider.isPresent() && ListableBeanFactory.class.isInstance(beanFactory)) {
|
||||
this.evaluationContextProvider = Optional
|
||||
.of(new ExtensionAwareQueryMethodEvaluationContextProvider((ListableBeanFactory) beanFactory));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -265,7 +271,8 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>,
|
||||
this.factory = createRepositoryFactory();
|
||||
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
|
||||
this.factory.setNamedQueries(namedQueries);
|
||||
this.factory.setEvaluationContextProvider(evaluationContextProvider);
|
||||
this.factory.setEvaluationContextProvider(
|
||||
evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));
|
||||
this.factory.setBeanClassLoader(classLoader);
|
||||
this.factory.setBeanFactory(beanFactory);
|
||||
|
||||
|
||||
@@ -51,11 +51,10 @@ import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
|
||||
import org.springframework.data.repository.query.DefaultEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.EvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
import org.springframework.data.repository.query.QueryMethod;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.util.ClassUtils;
|
||||
import org.springframework.data.repository.util.ReactiveWrapperConverters;
|
||||
@@ -119,7 +118,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
private List<QueryCreationListener<?>> queryPostProcessors;
|
||||
private NamedQueries namedQueries;
|
||||
private ClassLoader classLoader;
|
||||
private EvaluationContextProvider evaluationContextProvider;
|
||||
private QueryMethodEvaluationContextProvider evaluationContextProvider;
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private final QueryCollectingQueryCreationListener collectingListener = new QueryCollectingQueryCreationListener();
|
||||
@@ -133,7 +132,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
this.repositoryBaseClass = Optional.empty();
|
||||
this.namedQueries = PropertiesBasedNamedQueries.EMPTY;
|
||||
this.classLoader = org.springframework.util.ClassUtils.getDefaultClassLoader();
|
||||
this.evaluationContextProvider = DefaultEvaluationContextProvider.INSTANCE;
|
||||
this.evaluationContextProvider = QueryMethodEvaluationContextProvider.DEFAULT;
|
||||
this.queryPostProcessors = new ArrayList<>();
|
||||
this.queryPostProcessors.add(collectingListener);
|
||||
}
|
||||
@@ -175,13 +174,14 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link EvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined queries.
|
||||
* Sets the {@link QueryMethodEvaluationContextProvider} to be used to evaluate SpEL expressions in manually defined
|
||||
* queries.
|
||||
*
|
||||
* @param evaluationContextProvider can be {@literal null}, defaults to
|
||||
* {@link DefaultEvaluationContextProvider#INSTANCE}.
|
||||
* {@link DefaultQueryMethodEvaluationContextProvider#INSTANCE}.
|
||||
*/
|
||||
public void setEvaluationContextProvider(EvaluationContextProvider evaluationContextProvider) {
|
||||
this.evaluationContextProvider = evaluationContextProvider == null ? DefaultEvaluationContextProvider.INSTANCE
|
||||
public void setEvaluationContextProvider(QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
this.evaluationContextProvider = evaluationContextProvider == null ? QueryMethodEvaluationContextProvider.DEFAULT
|
||||
: evaluationContextProvider;
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
protected abstract Class<?> getRepositoryBaseClass(RepositoryMetadata metadata);
|
||||
|
||||
/**
|
||||
* Returns the {@link QueryLookupStrategy} for the given {@link Key} and {@link EvaluationContextProvider}.
|
||||
* Returns the {@link QueryLookupStrategy} for the given {@link Key} and {@link QueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param key can be {@literal null}.
|
||||
* @param evaluationContextProvider will never be {@literal null}.
|
||||
@@ -439,7 +439,7 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
|
||||
* @since 1.9
|
||||
*/
|
||||
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(@Nullable Key key,
|
||||
EvaluationContextProvider evaluationContextProvider) {
|
||||
QueryMethodEvaluationContextProvider evaluationContextProvider) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2014-2018 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.repository.query;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link QueryMethodEvaluationContextProvider} that assembles an {@link EvaluationContext} from a list of
|
||||
* {@link EvaluationContextExtension} instances.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @since 1.9
|
||||
*/
|
||||
public class ExtensionAwareQueryMethodEvaluationContextProvider implements QueryMethodEvaluationContextProvider {
|
||||
|
||||
private final Lazy<org.springframework.data.spel.ExtensionAwareEvaluationContextProvider> delegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwareQueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @param beanFactory the {@link ListableBeanFactory} to lookup the {@link EvaluationContextExtension}s from, must not
|
||||
* be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public ExtensionAwareQueryMethodEvaluationContextProvider(ListableBeanFactory beanFactory) {
|
||||
|
||||
Assert.notNull(beanFactory, "ListableBeanFactory must not be null!");
|
||||
|
||||
this.delegate = Lazy.of(() -> {
|
||||
|
||||
org.springframework.data.spel.ExtensionAwareEvaluationContextProvider delegate = new org.springframework.data.spel.ExtensionAwareEvaluationContextProvider(
|
||||
() -> getExtensionsFrom(beanFactory));
|
||||
delegate.setBeanFactory(beanFactory);
|
||||
|
||||
return delegate;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwareQueryMethodEvaluationContextProvider} using the given
|
||||
* {@link EvaluationContextExtension}s.
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwareQueryMethodEvaluationContextProvider(List<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
Assert.notNull(extensions, "EvaluationContextExtensions must not be null!");
|
||||
|
||||
this.delegate = Lazy.of(new org.springframework.data.spel.ExtensionAwareEvaluationContextProvider(extensions));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.QueryMethodEvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[])
|
||||
*/
|
||||
@Override
|
||||
public <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues) {
|
||||
|
||||
StandardEvaluationContext evaluationContext = delegate.get().getEvaluationContext(parameterValues);
|
||||
|
||||
evaluationContext.setVariables(collectVariables(parameters, parameterValues));
|
||||
|
||||
return evaluationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes variables for all named parameters for the given arguments. Also exposes non-bindable parameters under the
|
||||
* names of their types.
|
||||
*
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param arguments must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static Map<String, Object> collectVariables(Streamable<? extends Parameter> parameters, Object[] arguments) {
|
||||
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
parameters.stream()//
|
||||
.filter(Parameter::isSpecialParameter)//
|
||||
.forEach(it -> variables.put(//
|
||||
StringUtils.uncapitalize(it.getType().getSimpleName()), //
|
||||
arguments[it.getIndex()]));
|
||||
|
||||
parameters.stream()//
|
||||
.filter(Parameter::isNamedParameter)//
|
||||
.forEach(it -> variables.put(//
|
||||
it.getName().orElseThrow(() -> new IllegalStateException("Should never occur!")), //
|
||||
arguments[it.getIndex()]));
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all {@link EvaluationContextExtension} and
|
||||
* {@link org.springframework.data.repository.query.spi.EvaluationContextExtension} instances from the given
|
||||
* {@link ListableBeanFactory} and wraps the latter into proxies so that they implement the former.
|
||||
*
|
||||
* @param beanFactory must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private static List<EvaluationContextExtension> getExtensionsFrom(ListableBeanFactory beanFactory) {
|
||||
|
||||
Stream<EvaluationContextExtension> legacyExtensions = beanFactory //
|
||||
.getBeansOfType(org.springframework.data.repository.query.spi.EvaluationContextExtension.class, true, false)
|
||||
.values().stream() //
|
||||
.map(ExtensionAwareQueryMethodEvaluationContextProvider::adaptFromLegacyApi);
|
||||
|
||||
Stream<EvaluationContextExtension> extensions = beanFactory
|
||||
.getBeansOfType(EvaluationContextExtension.class, true, false).values().stream();
|
||||
|
||||
return Stream.concat(extensions, legacyExtensions).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "deprecation", "unchecked" })
|
||||
private static final EvaluationContextExtension adaptFromLegacyApi(
|
||||
org.springframework.data.repository.query.spi.EvaluationContextExtension extension) {
|
||||
|
||||
DelegatingMethodInterceptor advice = new DelegatingMethodInterceptor(extension);
|
||||
advice.registerResultMapping("getFunctions",
|
||||
result -> ((Map<String, org.springframework.data.repository.query.spi.Function>) result).entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().toNewFunction())));
|
||||
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setInterfaces(EvaluationContextExtension.class);
|
||||
factory.setTargetClass(extension.getClass());
|
||||
factory.setProxyTargetClass(true);
|
||||
factory.setTarget(extension);
|
||||
factory.addAdvice(advice);
|
||||
|
||||
return (EvaluationContextExtension) factory.getProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that forwards all invocations of methods (by name and parameter types) that are
|
||||
* available on a given target object
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
static class DelegatingMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private static final Map<Method, Method> METHOD_CACHE = new ConcurrentReferenceHashMap<Method, Method>();
|
||||
|
||||
private final Object target;
|
||||
private final Map<String, java.util.function.Function<Object, Object>> directMappings = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Registers a result mapping for the method with the given name. Invocation results for matching methods will be
|
||||
* piped through the mapping.
|
||||
*
|
||||
* @param methodName
|
||||
* @param mapping
|
||||
*/
|
||||
public void registerResultMapping(String methodName, java.util.function.Function<Object, Object> mapping) {
|
||||
this.directMappings.put(methodName, mapping);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(@Nullable MethodInvocation invocation) throws Throwable {
|
||||
|
||||
if (invocation == null) {
|
||||
throw new IllegalArgumentException("Invocation must not be null!");
|
||||
}
|
||||
|
||||
Method method = invocation.getMethod();
|
||||
Method targetMethod = METHOD_CACHE.computeIfAbsent(method,
|
||||
it -> Optional.ofNullable(findTargetMethod(it)).orElse(it));
|
||||
|
||||
Object result = method.equals(targetMethod) ? invocation.proceed()
|
||||
: ReflectionUtils.invokeMethod(targetMethod, target, invocation.getArguments());
|
||||
|
||||
if (result == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
java.util.function.Function<Object, Object> mapper = directMappings.get(targetMethod.getName());
|
||||
|
||||
return mapper != null ? mapper.apply(result) : result;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Method findTargetMethod(Method method) {
|
||||
|
||||
try {
|
||||
return target.getClass().getMethod(method.getName(), method.getParameterTypes());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
|
||||
@@ -26,7 +28,10 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
* @author Christoph Strobl
|
||||
* @since 1.9
|
||||
*/
|
||||
public interface EvaluationContextProvider {
|
||||
public interface QueryMethodEvaluationContextProvider {
|
||||
|
||||
static QueryMethodEvaluationContextProvider DEFAULT = new ExtensionAwareQueryMethodEvaluationContextProvider(
|
||||
Collections.emptyList());
|
||||
|
||||
/**
|
||||
* Returns an {@link EvaluationContext} built using the given {@link Parameters} and parameter values.
|
||||
@@ -15,20 +15,23 @@
|
||||
*/
|
||||
package org.springframework.data.repository.query.spi;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider;
|
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* SPI to allow adding a set of properties and function definitions accessible via the root of an
|
||||
* {@link EvaluationContext} provided by a {@link ExtensionAwareEvaluationContextProvider}.
|
||||
* {@link EvaluationContext} provided by a {@link ExtensionAwareQueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
* @deprecated since 2.1. Switch to same types in {@code org.springframework.data.spel.spi}.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface EvaluationContextExtension {
|
||||
|
||||
/**
|
||||
@@ -44,14 +47,18 @@ public interface EvaluationContextExtension {
|
||||
*
|
||||
* @return the properties
|
||||
*/
|
||||
Map<String, Object> getProperties();
|
||||
default Map<String, Object> getProperties() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the functions exposed by the extension.
|
||||
*
|
||||
* @return the functions
|
||||
*/
|
||||
Map<String, Function> getFunctions();
|
||||
default Map<String, Function> getFunctions() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root object to be exposed by the extension. It's strongly recommended to declare the most concrete type
|
||||
@@ -61,5 +68,7 @@ public interface EvaluationContextExtension {
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
Object getRootObject();
|
||||
default Object getRootObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ import org.springframework.lang.Nullable;
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @see 1.9
|
||||
* @deprecated since 2.1. Implement {@link EvaluationContextExtension} directly.
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class EvaluationContextExtensionSupport implements EvaluationContextExtension {
|
||||
|
||||
/*
|
||||
|
||||
@@ -33,7 +33,9 @@ import org.springframework.util.TypeUtils;
|
||||
* @author Oliver Gierke
|
||||
* @author Jens Schauder
|
||||
* @since 1.9
|
||||
* @deprecated since 2.1. Switch to same types in {@code org.springframework.data.spel.spi}.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Function {
|
||||
|
||||
private final Method method;
|
||||
@@ -67,6 +69,15 @@ public class Function {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link org.springframework.data.spel.spi.Function} from the legacy instance.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public org.springframework.data.spel.spi.Function toNewFunction() {
|
||||
return new org.springframework.data.spel.spi.Function(method, target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the function with the given arguments.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
package org.springframework.data.spel;
|
||||
|
||||
import static org.springframework.data.util.StreamUtils.*;
|
||||
|
||||
@@ -33,10 +33,10 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.ExtensionTypeInformation.PublicMethodAndFieldFilter;
|
||||
import org.springframework.data.repository.query.Functions.NameAndArgumentCount;
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.repository.query.spi.Function;
|
||||
import org.springframework.data.spel.EvaluationContextExtensionInformation.ExtensionTypeInformation.PublicMethodAndFieldFilter;
|
||||
import org.springframework.data.spel.Functions.NameAndArgumentCount;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.spel.spi.Function;
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -58,7 +58,7 @@ import org.springframework.util.ReflectionUtils.MethodFilter;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @since 1.9
|
||||
* @since 2.1
|
||||
*/
|
||||
class EvaluationContextExtensionInformation {
|
||||
|
||||
@@ -13,32 +13,34 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
package org.springframework.data.spel;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link EvaluationContextProvider} that always creates a new {@link EvaluationContext}.
|
||||
* Provides a way to access a centrally defined potentially shared {@link StandardEvaluationContext}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @since 1.9
|
||||
* @since 2.1
|
||||
*/
|
||||
public enum DefaultEvaluationContextProvider implements EvaluationContextProvider {
|
||||
public interface EvaluationContextProvider {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.EvaluationContextProvider#getEvaluationContext(org.springframework.data.repository.query.Parameters, java.lang.Object[])
|
||||
/**
|
||||
* A simple default {@link EvaluationContextProvider} returning a {@link StandardEvaluationContext} with the given
|
||||
* root object.
|
||||
*/
|
||||
@Override
|
||||
public <T extends Parameters<?, ?>> EvaluationContext getEvaluationContext(T parameters, Object[] parameterValues) {
|
||||
static EvaluationContextProvider DEFAULT = rootObject -> rootObject == null //
|
||||
? new StandardEvaluationContext() //
|
||||
: new StandardEvaluationContext(rootObject);
|
||||
|
||||
return ObjectUtils.isEmpty(parameterValues) ? new StandardEvaluationContext() : new StandardEvaluationContext(
|
||||
parameterValues);
|
||||
}
|
||||
/**
|
||||
* Returns an {@link EvaluationContext} built using the given parameter values.
|
||||
*
|
||||
* @param rootObject the root object to set in the {@link EvaluationContext}.
|
||||
* @return
|
||||
*/
|
||||
EvaluationContext getEvaluationContext(Object rootObject);
|
||||
}
|
||||
@@ -13,33 +13,29 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
package org.springframework.data.spel;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.ExtensionTypeInformation;
|
||||
import org.springframework.data.repository.query.EvaluationContextExtensionInformation.RootObjectInformation;
|
||||
import org.springframework.data.repository.query.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.repository.query.spi.Function;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.data.spel.EvaluationContextExtensionInformation.ExtensionTypeInformation;
|
||||
import org.springframework.data.spel.EvaluationContextExtensionInformation.RootObjectInformation;
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.data.spel.spi.Function;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -53,7 +49,6 @@ import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link EvaluationContextProvider} that assembles an {@link EvaluationContext} from a list of
|
||||
@@ -63,21 +58,30 @@ import org.springframework.util.StringUtils;
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
* @author Jens Schauder
|
||||
* @since 1.9
|
||||
* @since 2.1
|
||||
*/
|
||||
public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider, ApplicationContextAware {
|
||||
@RequiredArgsConstructor
|
||||
public class ExtensionAwareEvaluationContextProvider implements EvaluationContextProvider {
|
||||
|
||||
private final Map<Class<?>, EvaluationContextExtensionInformation> extensionInformationCache = new HashMap<>();
|
||||
|
||||
private final Lazy<List<? extends EvaluationContextExtension>> extensions;
|
||||
private Optional<ListableBeanFactory> beanFactory = Optional.empty();
|
||||
private final Supplier<? extends Collection<? extends EvaluationContextExtension>> extensions;
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
ExtensionAwareEvaluationContextProvider() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ExtensionAwareEvaluationContextProvider}. Extensions are being looked up lazily from the
|
||||
* {@link BeanFactory} configured.
|
||||
* Creates a new {@link ExtensionAwareEvaluationContextProvider} with extensions looked up lazily from the given
|
||||
* {@link BeanFactory}.
|
||||
*
|
||||
* @param beanFactory the {@link ListableBeanFactory} to lookup extensions from.
|
||||
*/
|
||||
public ExtensionAwareEvaluationContextProvider() {
|
||||
this.extensions = Lazy.of(() -> getExtensionsFrom(beanFactory));
|
||||
public ExtensionAwareEvaluationContextProvider(ListableBeanFactory beanFactory) {
|
||||
|
||||
this(() -> getExtensionsFrom(beanFactory));
|
||||
this.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,31 +89,32 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwareEvaluationContextProvider(List<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
Assert.notNull(extensions, "List of EvaluationContextExtensions must not be null!");
|
||||
this.extensions = Lazy.of(() -> extensions);
|
||||
public ExtensionAwareEvaluationContextProvider(Collection<? extends EvaluationContextExtension> extensions) {
|
||||
this(() -> extensions);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
/**
|
||||
* Sets the {@link ListableBeanFactory} to be used on the {@link EvaluationContext} to be created.
|
||||
*
|
||||
* @param beanFactory
|
||||
* @deprecated only exists to temporarily mitigate from the old APIs. Do not use!
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.beanFactory = Optional.of(applicationContext);
|
||||
@Deprecated
|
||||
public void setBeanFactory(ListableBeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.jpa.repository.support.EvaluationContextProvider#getEvaluationContext()
|
||||
*/
|
||||
@Override
|
||||
public <T extends Parameters<?, ?>> StandardEvaluationContext getEvaluationContext(T parameters,
|
||||
Object[] parameterValues) {
|
||||
public StandardEvaluationContext getEvaluationContext(Object rootObject) {
|
||||
|
||||
StandardEvaluationContext ec = new StandardEvaluationContext();
|
||||
|
||||
beanFactory.ifPresent(it -> ec.setBeanResolver(new BeanFactoryResolver(it)));
|
||||
if (beanFactory != null) {
|
||||
ec.setBeanResolver(new BeanFactoryResolver(beanFactory));
|
||||
}
|
||||
|
||||
ExtensionAwarePropertyAccessor accessor = new ExtensionAwarePropertyAccessor(extensions.get());
|
||||
|
||||
@@ -117,54 +122,21 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
ec.addPropertyAccessor(new ReflectivePropertyAccessor());
|
||||
ec.addMethodResolver(accessor);
|
||||
|
||||
// Add parameters for indexed access
|
||||
ec.setRootObject(parameterValues);
|
||||
ec.setVariables(collectVariables(parameters, parameterValues));
|
||||
if (rootObject != null) {
|
||||
ec.setRootObject(rootObject);
|
||||
}
|
||||
|
||||
return ec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes variables for all named parameters for the given arguments. Also exposes non-bindable parameters under the
|
||||
* names of their types.
|
||||
*
|
||||
* @param parameters must not be {@literal null}.
|
||||
* @param arguments must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private <T extends Parameters<?, ?>> Map<String, Object> collectVariables(T parameters, Object[] arguments) {
|
||||
|
||||
Map<String, Object> variables = new HashMap<>();
|
||||
|
||||
parameters.stream()//
|
||||
.filter(Parameter::isSpecialParameter)//
|
||||
.forEach(it -> variables.put(//
|
||||
StringUtils.uncapitalize(it.getType().getSimpleName()), //
|
||||
arguments[it.getIndex()]));
|
||||
|
||||
parameters.stream()//
|
||||
.filter(Parameter::isNamedParameter)//
|
||||
.forEach(it -> variables.put(//
|
||||
it.getName().orElseThrow(() -> new IllegalStateException("Should never occur!")), //
|
||||
arguments[it.getIndex()]));
|
||||
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up all {@link EvaluationContextExtension} instances from the given {@link ListableBeanFactory}.
|
||||
*
|
||||
* @param beanFactory must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static List<? extends EvaluationContextExtension> getExtensionsFrom(
|
||||
Optional<ListableBeanFactory> beanFactory) {
|
||||
|
||||
Collection<? extends EvaluationContextExtension> extensions = beanFactory//
|
||||
.map(it -> it.getBeansOfType(EvaluationContextExtension.class, true, false).values())//
|
||||
.orElseGet(() -> Collections.emptyList());
|
||||
|
||||
return new ArrayList<>(extensions);
|
||||
private static Collection<? extends EvaluationContextExtension> getExtensionsFrom(ListableBeanFactory beanFactory) {
|
||||
return beanFactory.getBeansOfType(EvaluationContextExtension.class, true, false).values();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,7 +160,8 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
* @param extensions
|
||||
* @return
|
||||
*/
|
||||
private List<EvaluationContextExtensionAdapter> toAdapters(List<? extends EvaluationContextExtension> extensions) {
|
||||
private List<EvaluationContextExtensionAdapter> toAdapters(
|
||||
Collection<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
return extensions.stream()//
|
||||
.sorted(AnnotationAwareOrderComparator.INSTANCE)//
|
||||
@@ -211,7 +184,7 @@ public class ExtensionAwareEvaluationContextProvider implements EvaluationContex
|
||||
*
|
||||
* @param extensions must not be {@literal null}.
|
||||
*/
|
||||
public ExtensionAwarePropertyAccessor(List<? extends EvaluationContextExtension> extensions) {
|
||||
public ExtensionAwarePropertyAccessor(Collection<? extends EvaluationContextExtension> extensions) {
|
||||
|
||||
Assert.notNull(extensions, "Extensions must not be null!");
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.query;
|
||||
package org.springframework.data.spel;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
@@ -28,7 +28,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.repository.query.spi.Function;
|
||||
import org.springframework.data.spel.spi.Function;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.springframework.util.MultiValueMap;
|
||||
*
|
||||
* @author Jens Schauder
|
||||
* @author Oliver Gierke
|
||||
* @since 2.0
|
||||
* @since 2.1
|
||||
*/
|
||||
class Functions {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2014-2018 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.spel.spi;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.query.ExtensionAwareQueryMethodEvaluationContextProvider;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* SPI to allow adding a set of properties and function definitions accessible via the root of an
|
||||
* {@link EvaluationContext} provided by a {@link ExtensionAwareQueryMethodEvaluationContextProvider}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @since 1.9
|
||||
*/
|
||||
public interface EvaluationContextExtension {
|
||||
|
||||
/**
|
||||
* Returns the identifier of the extension. The id can be leveraged by users to fully qualify property lookups and
|
||||
* thus overcome ambiguities in case multiple extensions expose properties with the same name.
|
||||
*
|
||||
* @return the extension id, must not be {@literal null}.
|
||||
*/
|
||||
String getExtensionId();
|
||||
|
||||
/**
|
||||
* Returns the properties exposed by the extension.
|
||||
*
|
||||
* @return the properties
|
||||
*/
|
||||
default Map<String, Object> getProperties() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the functions exposed by the extension.
|
||||
*
|
||||
* @return the functions
|
||||
*/
|
||||
default Map<String, Function> getFunctions() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root object to be exposed by the extension. It's strongly recommended to declare the most concrete type
|
||||
* possible as return type of the implementation method. This will allow us to obtain the necessary metadata once and
|
||||
* not for every evaluation.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
default Object getRootObject() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
165
src/main/java/org/springframework/data/spel/spi/Function.java
Normal file
165
src/main/java/org/springframework/data/spel/spi/Function.java
Normal file
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* Copyright 2014-2018 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.spel.spi;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.TypeUtils;
|
||||
|
||||
/**
|
||||
* Value object to represent a function. Can either be backed by a static {@link Method} invocation (see
|
||||
* {@link #Function(Method)}) or a method invocation on an instance (see {@link #Function(Method, Object)}.
|
||||
*
|
||||
* @author Thomas Darimont
|
||||
* @author Oliver Gierke
|
||||
* @author Jens Schauder
|
||||
* @since 1.9
|
||||
*/
|
||||
public class Function {
|
||||
|
||||
private final Method method;
|
||||
private final @Nullable Object target;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Function} to statically invoke the given {@link Method}.
|
||||
*
|
||||
* @param method
|
||||
*/
|
||||
public Function(Method method) {
|
||||
|
||||
this(method, null);
|
||||
|
||||
Assert.isTrue(Modifier.isStatic(method.getModifiers()), "Method must be static!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Function} for the given method on the given target instance.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param target can be {@literal null}, if so, the method
|
||||
*/
|
||||
public Function(Method method, @Nullable Object target) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.isTrue(target != null || Modifier.isStatic(method.getModifiers()),
|
||||
"Method must either be static or a non-static one with a target object!");
|
||||
|
||||
this.method = method;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the function with the given arguments.
|
||||
*
|
||||
* @param arguments must not be {@literal null}.
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public Object invoke(Object[] arguments) throws Exception {
|
||||
return method.invoke(target, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the function.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getName() {
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type declaring the {@link Function}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Class<?> getDeclaringClass() {
|
||||
return method.getDeclaringClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@literal true} if the function can be called with the given {@code argumentTypes}.
|
||||
*
|
||||
* @param argumentTypes
|
||||
* @return
|
||||
*/
|
||||
public boolean supports(List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
if (method.getParameterCount() != argumentTypes.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
if (!TypeUtils.isAssignable(parameterTypes[i], argumentTypes.get(i).getType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of parameters required by the underlying method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getParameterCount() {
|
||||
return method.getParameterCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the encapsulated method has exactly the argument types as those passed as an argument.
|
||||
*
|
||||
* @param argumentTypes a list of {@link TypeDescriptor}s to compare with the argument types of the method
|
||||
* @return {@code true} if the types are equal, {@code false} otherwise.
|
||||
*/
|
||||
public boolean supportsExact(List<TypeDescriptor> argumentTypes) {
|
||||
|
||||
if (method.getParameterCount() != argumentTypes.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
if (parameterTypes[i] != argumentTypes.get(i).getType()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this {@code Function} has the same signature as another {@code Function}.
|
||||
*
|
||||
* @param other the {@code Function} to compare {@code this} with.
|
||||
* @return {@code true} if name and argument list are the same.
|
||||
*/
|
||||
public boolean isSignatureEqual(Function other) {
|
||||
|
||||
return getName().equals(other.getName()) //
|
||||
&& Arrays.equals(method.getParameterTypes(), other.method.getParameterTypes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Service provider interfaces to extend the query execution mechanism.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.spel.spi;
|
||||
Reference in New Issue
Block a user