Skip to content

Commit c4f91d2

Browse files
Rob Winchchristophstrobl
Rob Winch
authored andcommitted
DATAREDIS-425 - Add SpelIndexResolver
This commit adds support for adding an index using SpEL expressions. Original PR #160
1 parent 9299d86 commit c4f91d2

File tree

3 files changed

+376
-1
lines changed

3 files changed

+376
-1
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.core.convert;
17+
18+
import java.util.Collections;
19+
import java.util.HashSet;
20+
import java.util.Set;
21+
22+
import org.springframework.context.expression.BeanFactoryResolver;
23+
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
24+
import org.springframework.data.redis.core.index.IndexConfiguration;
25+
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
26+
import org.springframework.data.redis.core.mapping.RedisMappingContext;
27+
import org.springframework.data.util.TypeInformation;
28+
import org.springframework.expression.BeanResolver;
29+
import org.springframework.expression.Expression;
30+
import org.springframework.expression.spel.standard.SpelExpressionParser;
31+
import org.springframework.expression.spel.support.StandardEvaluationContext;
32+
import org.springframework.util.Assert;
33+
34+
/**
35+
* An {@link IndexResolver} that resolves {@link IndexedData} using a {@link SpelExpressionParser}.
36+
*
37+
* @author Rob Winch
38+
* @since 1.7
39+
*/
40+
public class SpelIndexResolver implements IndexResolver {
41+
42+
private final IndexConfiguration settings;
43+
private final SpelExpressionParser parser;
44+
private final RedisMappingContext mappingContext;
45+
46+
private BeanResolver beanResolver;
47+
48+
/**
49+
* Creates a new instance using a default {@link SpelExpressionParser}.
50+
*
51+
* @param mappingContext the {@link RedisMappingContext} to use. Cannot be null.
52+
*/
53+
public SpelIndexResolver(RedisMappingContext mappingContext) {
54+
this(mappingContext, new SpelExpressionParser());
55+
}
56+
57+
/**
58+
* Creates a new instance
59+
*
60+
* @param mappingContext the {@link RedisMappingContext} to use. Cannot be null.
61+
* @param parser the {@link SpelExpressionParser} to use. Cannot be null.
62+
*/
63+
public SpelIndexResolver(RedisMappingContext mappingContext, SpelExpressionParser parser) {
64+
65+
Assert.notNull(mappingContext, "RedisMappingContext must not be null!");
66+
Assert.notNull(parser, "SpelExpressionParser must not be null!");
67+
this.mappingContext = mappingContext;
68+
this.settings = mappingContext.getMappingConfiguration().getIndexConfiguration();
69+
this.parser = parser;
70+
}
71+
72+
/* (non-Javadoc)
73+
* @see org.springframework.data.redis.core.convert.IndexResolver#resolveIndexesFor(org.springframework.data.util.TypeInformation, java.lang.Object)
74+
*/
75+
public Set<IndexedData> resolveIndexesFor(TypeInformation<?> typeInformation, Object value) {
76+
77+
if (value == null) {
78+
return Collections.emptySet();
79+
}
80+
81+
KeyValuePersistentEntity<?> entity = mappingContext.getPersistentEntity(typeInformation);
82+
83+
if (entity == null) {
84+
return Collections.emptySet();
85+
}
86+
87+
String keyspace = entity.getKeySpace();
88+
89+
Set<IndexedData> indexes = new HashSet<IndexedData>();
90+
91+
for (RedisIndexSetting setting : settings.getIndexDefinitionsFor(keyspace)) {
92+
93+
Expression expression = parser.parseExpression(setting.getPath());
94+
StandardEvaluationContext context = new StandardEvaluationContext();
95+
context.setRootObject(value);
96+
context.setVariable("this", value);
97+
98+
if (beanResolver != null) {
99+
context.setBeanResolver(beanResolver);
100+
}
101+
102+
Object index = expression.getValue(context);
103+
if (index != null) {
104+
indexes.add(new SimpleIndexedPropertyValue(keyspace, setting.getIndexName(), index));
105+
}
106+
}
107+
108+
return indexes;
109+
}
110+
111+
/**
112+
* Allows setting the BeanResolver
113+
*
114+
* @param beanResolver can be {@literal null}.
115+
* @see BeanFactoryResolver
116+
*/
117+
public void setBeanResolver(BeanResolver beanResolver) {
118+
this.beanResolver = beanResolver;
119+
}
120+
}

src/main/java/org/springframework/data/redis/core/index/IndexConfiguration.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2015 the original author or authors.
2+
* Copyright 2015-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,6 +86,25 @@ public List<RedisIndexSetting> getIndexDefinitionsFor(Serializable keyspace, Str
8686
return indexDefinitions;
8787
}
8888

89+
/**
90+
* Gets all of the {@link RedisIndexSetting} for a given keyspace.
91+
*
92+
* @param keyspace the keyspace to get
93+
* @return never {@literal null}
94+
*/
95+
public List<RedisIndexSetting> getIndexDefinitionsFor(Serializable keyspace) {
96+
97+
List<RedisIndexSetting> indexDefinitions = new ArrayList<RedisIndexSetting>();
98+
99+
for (RedisIndexSetting indexDef : definitions) {
100+
if (indexDef.getKeyspace().equals(keyspace)) {
101+
indexDefinitions.add(indexDef);
102+
}
103+
}
104+
105+
return indexDefinitions;
106+
}
107+
89108
/**
90109
* Add given {@link RedisIndexSetting}.
91110
*
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright 2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.redis.core.convert;
17+
18+
import static org.hamcrest.CoreMatchers.*;
19+
import static org.junit.Assert.*;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import java.util.Set;
24+
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity;
28+
import org.springframework.data.redis.core.convert.KeyspaceConfiguration.KeyspaceSettings;
29+
import org.springframework.data.redis.core.index.IndexConfiguration;
30+
import org.springframework.data.redis.core.index.IndexConfiguration.RedisIndexSetting;
31+
import org.springframework.data.redis.core.index.IndexType;
32+
import org.springframework.data.redis.core.mapping.RedisMappingContext;
33+
import org.springframework.data.util.ClassTypeInformation;
34+
import org.springframework.expression.AccessException;
35+
import org.springframework.expression.BeanResolver;
36+
import org.springframework.expression.EvaluationContext;
37+
import org.springframework.expression.spel.SpelEvaluationException;
38+
39+
/**
40+
* @author Rob Winch
41+
* @author Christoph Strobl
42+
*/
43+
public class SpelIndexResolverUnitTests {
44+
45+
String keyspace;
46+
47+
String indexName;
48+
49+
String username;
50+
51+
SpelIndexResolver resolver;
52+
53+
Session session;
54+
55+
ClassTypeInformation<?> typeInformation;
56+
57+
String securityContextAttrName;
58+
59+
RedisMappingContext mappingContext;
60+
61+
KeyValuePersistentEntity<?> entity;
62+
63+
@Before
64+
public void setup() {
65+
66+
username = "rob";
67+
keyspace = "spring:session:sessions";
68+
indexName = "principalName";
69+
securityContextAttrName = "SPRING_SECURITY_CONTEXT";
70+
71+
typeInformation = ClassTypeInformation.from(Session.class);
72+
session = createSession();
73+
74+
resolver = createWithExpression("getAttribute('" + securityContextAttrName + "')?.authentication?.name");
75+
}
76+
77+
/**
78+
* @see DATAREDIS-425
79+
*/
80+
@Test(expected = IllegalArgumentException.class)
81+
public void constructorNullRedisMappingContext() {
82+
83+
mappingContext = null;
84+
new SpelIndexResolver(mappingContext);
85+
}
86+
87+
/**
88+
* @see DATAREDIS-425
89+
*/
90+
@Test(expected = IllegalArgumentException.class)
91+
public void constructorNullSpelExpressionParser() {
92+
new SpelIndexResolver(mappingContext, null);
93+
}
94+
95+
/**
96+
* @see DATAREDIS-425
97+
*/
98+
@Test
99+
public void nullValue() {
100+
101+
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, null);
102+
103+
assertThat(indexes.size(), equalTo(0));
104+
}
105+
106+
/**
107+
* @see DATAREDIS-425
108+
*/
109+
@Test
110+
public void wrongKeyspace() {
111+
112+
typeInformation = ClassTypeInformation.from(String.class);
113+
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, "");
114+
115+
assertThat(indexes.size(), equalTo(0));
116+
}
117+
118+
/**
119+
* @see DATAREDIS-425
120+
*/
121+
@Test
122+
public void sessionAttributeNull() {
123+
124+
session = new Session();
125+
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
126+
127+
assertThat(indexes.size(), equalTo(0));
128+
}
129+
130+
/**
131+
* @see DATAREDIS-425
132+
*/
133+
@Test
134+
public void resolvePrincipalName() {
135+
136+
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
137+
138+
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(keyspace, indexName, username)));
139+
}
140+
141+
/**
142+
* @see DATAREDIS-425
143+
*/
144+
@Test(expected = SpelEvaluationException.class)
145+
public void spelError() {
146+
147+
session.setAttribute(securityContextAttrName, "");
148+
149+
resolver.resolveIndexesFor(typeInformation, session);
150+
}
151+
152+
/**
153+
* @see DATAREDIS-425
154+
*/
155+
@Test
156+
public void withBeanAndThis() {
157+
158+
this.resolver = createWithExpression("@bean.run(#this)");
159+
this.resolver.setBeanResolver(new BeanResolver() {
160+
@Override
161+
public Object resolve(EvaluationContext context, String beanName) throws AccessException {
162+
return new Object() {
163+
@SuppressWarnings("unused")
164+
public Object run(Object arg) {
165+
return arg;
166+
}
167+
};
168+
}
169+
});
170+
171+
Set<IndexedData> indexes = resolver.resolveIndexesFor(typeInformation, session);
172+
173+
assertThat(indexes, hasItem(new SimpleIndexedPropertyValue(keyspace, indexName, session)));
174+
}
175+
176+
private SpelIndexResolver createWithExpression(String expression) {
177+
178+
RedisIndexSetting principalIndex = new RedisIndexSetting(keyspace, expression, indexName, IndexType.SIMPLE);
179+
IndexConfiguration configuration = new IndexConfiguration();
180+
configuration.addIndexDefinition(principalIndex);
181+
182+
KeyspaceSettings keyspaceSettings = new KeyspaceSettings(Session.class, keyspace);
183+
KeyspaceConfiguration keyspaceConfiguration = new KeyspaceConfiguration();
184+
keyspaceConfiguration.addKeyspaceSettings(keyspaceSettings);
185+
186+
MappingConfiguration mapping = new MappingConfiguration(configuration, keyspaceConfiguration);
187+
188+
mappingContext = new RedisMappingContext(mapping);
189+
190+
return new SpelIndexResolver(mappingContext);
191+
}
192+
193+
private Session createSession() {
194+
195+
Session session = new Session();
196+
session.setAttribute(securityContextAttrName, new SecurityContextImpl(new Authentication(username)));
197+
return session;
198+
}
199+
200+
static class Session {
201+
202+
private Map<String, Object> sessionAttrs = new HashMap<String, Object>();
203+
204+
public void setAttribute(String attrName, Object attrValue) {
205+
this.sessionAttrs.put(attrName, attrValue);
206+
}
207+
208+
public Object getAttribute(String attributeName) {
209+
return sessionAttrs.get(attributeName);
210+
}
211+
}
212+
213+
static class SecurityContextImpl {
214+
private final Authentication authentication;
215+
216+
public SecurityContextImpl(Authentication authentication) {
217+
this.authentication = authentication;
218+
}
219+
220+
public Authentication getAuthentication() {
221+
return authentication;
222+
}
223+
}
224+
225+
public static class Authentication {
226+
private final String principalName;
227+
228+
public Authentication(String principalName) {
229+
this.principalName = principalName;
230+
}
231+
232+
public String getName() {
233+
return principalName;
234+
}
235+
}
236+
}

0 commit comments

Comments
 (0)