DATAREDIS-425 - Add SpelIndexResolver
This commit adds support for adding an index using SpEL expressions. Original PR #160
This commit is contained in:
committed by
Christoph Strobl
parent
40c2582302
commit
059ac9fabb
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2016 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.redis.core.convert;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.context.expression.BeanFactoryResolver;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link IndexResolver} that resolves {@link IndexedData} using a {@link SpelExpressionParser}.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 1.7
|
||||
*/
|
||||
public class SpelIndexResolver implements IndexResolver {
|
||||
|
||||
private final IndexConfiguration settings;
|
||||
private final SpelExpressionParser parser;
|
||||
private final RedisMappingContext mappingContext;
|
||||
|
||||
private BeanResolver beanResolver;
|
||||
|
||||
/**
|
||||
* Creates a new instance using a default {@link SpelExpressionParser}.
|
||||
*
|
||||
* @param mappingContext the {@link RedisMappingContext} to use. Cannot be null.
|
||||
*/
|
||||
public SpelIndexResolver(RedisMappingContext mappingContext) {
|
||||
this(mappingContext, new SpelExpressionParser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param mappingContext the {@link RedisMappingContext} to use. Cannot be null.
|
||||
* @param parser the {@link SpelExpressionParser} to use. Cannot be null.
|
||||
*/
|
||||
public SpelIndexResolver(RedisMappingContext mappingContext, SpelExpressionParser parser) {
|
||||
|
||||
Assert.notNull(mappingContext, "RedisMappingContext must not be null!");
|
||||
Assert.notNull(parser, "SpelExpressionParser must not be null!");
|
||||
this.mappingContext = mappingContext;
|
||||
this.settings = mappingContext.getMappingConfiguration().getIndexConfiguration();
|
||||
this.parser = parser;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.core.convert.IndexResolver#resolveIndexesFor(org.springframework.data.util.TypeInformation, java.lang.Object)
|
||||
*/
|
||||
public Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Object value) {
|
||||
|
||||
if (value == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
KeyValuePersistentEntity<?> entity = mappingContext.getPersistentEntity(typeInformation);
|
||||
|
||||
if (entity == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
String keyspace = entity.getKeySpace();
|
||||
|
||||
Set<IndexedData> indexes = new HashSet<IndexedData>();
|
||||
|
||||
for (RedisIndexSetting setting : settings.getIndexDefinitionsFor(keyspace)) {
|
||||
|
||||
Expression expression = parser.parseExpression(setting.getPath());
|
||||
StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
context.setRootObject(value);
|
||||
context.setVariable("this", value);
|
||||
|
||||
if (beanResolver != null) {
|
||||
context.setBeanResolver(beanResolver);
|
||||
}
|
||||
|
||||
Object index = expression.getValue(context);
|
||||
if (index != null) {
|
||||
indexes.add(new SimpleIndexedPropertyValue(keyspace, setting.getIndexName(), index));
|
||||
}
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows setting the BeanResolver
|
||||
*
|
||||
* @param beanResolver can be {@literal null}.
|
||||
* @see BeanFactoryResolver
|
||||
*/
|
||||
public void setBeanResolver(BeanResolver beanResolver) {
|
||||
this.beanResolver = beanResolver;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
* Copyright 2015-2016 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.
|
||||
@@ -86,6 +86,25 @@ public class IndexConfiguration {
|
||||
return indexDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all of the {@link RedisIndexSetting} for a given keyspace.
|
||||
*
|
||||
* @param keyspace the keyspace to get
|
||||
* @return never {@literal null}
|
||||
*/
|
||||
public List<RedisIndexSetting> getIndexDefinitionsFor(Serializable keyspace) {
|
||||
|
||||
List<RedisIndexSetting> indexDefinitions = new ArrayList<RedisIndexSetting>();
|
||||
|
||||
for (RedisIndexSetting indexDef : definitions) {
|
||||
if (indexDef.getKeyspace().equals(keyspace)) {
|
||||
indexDefinitions.add(indexDef);
|
||||
}
|
||||
}
|
||||
|
||||
return indexDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add given {@link RedisIndexSetting}.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright 2016 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.redis.core.convert;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
|
||||
import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration;
|
||||
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
|
||||
import org.springframework.data.redis.core.index.IndexType;
|
||||
import org.springframework.data.redis.core.mapping.RedisMappingContext;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.BeanResolver;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
public class SpelIndexResolverUnitTests {
|
||||
|
||||
String keyspace;
|
||||
|
||||
String indexName;
|
||||
|
||||
String username;
|
||||
|
||||
SpelIndexResolver resolver;
|
||||
|
||||
Session session;
|
||||
|
||||
ClassTypeInformation<?> typeInformation;
|
||||
|
||||
String securityContextAttrName;
|
||||
|
||||
RedisMappingContext mappingContext;
|
||||
|
||||
KeyValuePersistentEntity<?> entity;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
username = "rob";
|
||||
keyspace = "spring:session:sessions";
|
||||
indexName = "principalName";
|
||||
securityContextAttrName = "SPRING_SECURITY_CONTEXT";
|
||||
|
||||
typeInformation = ClassTypeInformation.from(Session.class);
|
||||
session = createSession();
|
||||
|
||||
resolver = createWithExpression("getAttribute('" + securityContextAttrName + "')?.authentication?.name");
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullRedisMappingContext() {
|
||||
|
||||
mappingContext = null;
|
||||
new SpelIndexResolver(mappingContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void constructorNullSpelExpressionParser() {
|
||||
new SpelIndexResolver(mappingContext, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test
|
||||
public void nullValue() {
|
||||
|
||||
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, null);
|
||||
|
||||
assertThat(indexes.size(), equalTo(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test
|
||||
public void wrongKeyspace() {
|
||||
|
||||
typeInformation = ClassTypeInformation.from(String.class);
|
||||
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, "");
|
||||
|
||||
assertThat(indexes.size(), equalTo(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test
|
||||
public void sessionAttributeNull() {
|
||||
|
||||
session = new Session();
|
||||
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
|
||||
|
||||
assertThat(indexes.size(), equalTo(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test
|
||||
public void resolvePrincipalName() {
|
||||
|
||||
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
|
||||
|
||||
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(keyspace, indexName, username)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void spelError() {
|
||||
|
||||
session.setAttribute(securityContextAttrName, "");
|
||||
|
||||
resolver.resolveIndexesFor(typeInformation, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-425
|
||||
*/
|
||||
@Test
|
||||
public void withBeanAndThis() {
|
||||
|
||||
this.resolver = createWithExpression("@bean.run(#this)");
|
||||
this.resolver.setBeanResolver(new BeanResolver() {
|
||||
@Override
|
||||
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
|
||||
return new Object() {
|
||||
@SuppressWarnings("unused")
|
||||
public Object run(Object arg) {
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
|
||||
|
||||
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(keyspace, indexName, session)));
|
||||
}
|
||||
|
||||
private SpelIndexResolver createWithExpression(String expression) {
|
||||
|
||||
RedisIndexSetting principalIndex = new RedisIndexSetting(keyspace, expression, indexName, IndexType.SIMPLE);
|
||||
IndexConfiguration configuration = new IndexConfiguration();
|
||||
configuration.addIndexDefinition(principalIndex);
|
||||
|
||||
KeyspaceSettings keyspaceSettings = new KeyspaceSettings(Session.class, keyspace);
|
||||
KeyspaceConfiguration keyspaceConfiguration = new KeyspaceConfiguration();
|
||||
keyspaceConfiguration.addKeyspaceSettings(keyspaceSettings);
|
||||
|
||||
MappingConfiguration mapping = new MappingConfiguration(configuration, keyspaceConfiguration);
|
||||
|
||||
mappingContext = new RedisMappingContext(mapping);
|
||||
|
||||
return new SpelIndexResolver(mappingContext);
|
||||
}
|
||||
|
||||
private Session createSession() {
|
||||
|
||||
Session session = new Session();
|
||||
session.setAttribute(securityContextAttrName, new SecurityContextImpl(new Authentication(username)));
|
||||
return session;
|
||||
}
|
||||
|
||||
static class Session {
|
||||
|
||||
private Map<String, Object> sessionAttrs = new HashMap<String, Object>();
|
||||
|
||||
public void setAttribute(String attrName, Object attrValue) {
|
||||
this.sessionAttrs.put(attrName, attrValue);
|
||||
}
|
||||
|
||||
public Object getAttribute(String attributeName) {
|
||||
return sessionAttrs.get(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
static class SecurityContextImpl {
|
||||
private final Authentication authentication;
|
||||
|
||||
public SecurityContextImpl(Authentication authentication) {
|
||||
this.authentication = authentication;
|
||||
}
|
||||
|
||||
public Authentication getAuthentication() {
|
||||
return authentication;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Authentication {
|
||||
private final String principalName;
|
||||
|
||||
public Authentication(String principalName) {
|
||||
this.principalName = principalName;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return principalName;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user