Skip to content
This repository was archived by the owner on Aug 29, 2024. It is now read-only.

Commit a1f7dd3

Browse files
dsibiliokushagraThapar
authored andcommitted
Enable SpEL expressions for @document collection name (#390)
* Evaluate SpEL expressions for @document collection name * Polish SpEL expression resolution for @document collection * DocumentDbFactory renamed to CosmosDbFactory * Fix broken JUnit tests due to wrong ObjectMapper
1 parent b500a9a commit a1f7dd3

File tree

11 files changed

+298
-2
lines changed

11 files changed

+298
-2
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
7+
package com.microsoft.azure.spring.data.cosmosdb.common;
8+
9+
import org.springframework.beans.factory.BeanFactory;
10+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
11+
import org.springframework.beans.factory.config.EmbeddedValueResolver;
12+
13+
/**
14+
*
15+
* @author Domenico Sibilio
16+
*
17+
*/
18+
public class ExpressionResolver {
19+
20+
private static EmbeddedValueResolver embeddedValueResolver;
21+
22+
public ExpressionResolver(BeanFactory beanFactory) {
23+
if (beanFactory instanceof ConfigurableBeanFactory) {
24+
setEmbeddedValueResolver(new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory));
25+
}
26+
}
27+
28+
/**
29+
* Resolve the given string value via an {@link EmbeddedValueResolver}
30+
* @param expression the expression to be resolved
31+
* @return the resolved expression, may be {@literal null}
32+
*/
33+
public static String resolveExpression(String expression) {
34+
return embeddedValueResolver != null
35+
? embeddedValueResolver.resolveStringValue(expression)
36+
: expression;
37+
}
38+
39+
private static void setEmbeddedValueResolver(EmbeddedValueResolver embeddedValueResolver) {
40+
ExpressionResolver.embeddedValueResolver = embeddedValueResolver;
41+
}
42+
43+
}

src/main/java/com/microsoft/azure/spring/data/cosmosdb/config/DocumentDbConfigurationSupport.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
package com.microsoft.azure.spring.data.cosmosdb.config;
88

9+
import com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver;
910
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
11+
12+
import org.springframework.beans.factory.BeanFactory;
1013
import org.springframework.beans.factory.config.BeanDefinition;
1114
import org.springframework.context.annotation.Bean;
1215
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
@@ -21,10 +24,19 @@
2124
import org.springframework.util.ClassUtils;
2225
import org.springframework.util.StringUtils;
2326

24-
import java.util.*;
27+
import java.util.Arrays;
28+
import java.util.Collection;
29+
import java.util.Collections;
30+
import java.util.HashSet;
31+
import java.util.Set;
2532

2633
public abstract class DocumentDbConfigurationSupport {
2734

35+
@Bean
36+
public ExpressionResolver expressionResolver(BeanFactory beanFactory) {
37+
return new ExpressionResolver(beanFactory);
38+
}
39+
2840
@Bean
2941
public DocumentDbMappingContext documentDbMappingContext() throws ClassNotFoundException {
3042
final DocumentDbMappingContext mappingContext = new DocumentDbMappingContext();

src/main/java/com/microsoft/azure/spring/data/cosmosdb/repository/support/DocumentDbEntityInformation.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentIndexingPolicy;
1616
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
1717
import org.apache.commons.lang3.reflect.FieldUtils;
18+
1819
import org.springframework.data.annotation.Id;
1920
import org.springframework.data.repository.core.support.AbstractEntityInformation;
2021
import org.springframework.lang.NonNull;
2122
import org.springframework.util.ReflectionUtils;
2223

24+
import static com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver.resolveExpression;
25+
2326
import java.lang.reflect.Field;
2427
import java.util.ArrayList;
2528
import java.util.Collection;
@@ -135,7 +138,7 @@ private String getCollectionName(Class<?> domainClass) {
135138
final Document annotation = domainClass.getAnnotation(Document.class);
136139

137140
if (annotation != null && annotation.collection() != null && !annotation.collection().isEmpty()) {
138-
customCollectionName = annotation.collection();
141+
customCollectionName = resolveExpression(annotation.collection());
139142
}
140143

141144
return customCollectionName;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.spring.data.cosmosdb.common;
7+
8+
import org.junit.Test;
9+
10+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
11+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.assertNotNull;
18+
19+
/**
20+
*
21+
* @author Domenico Sibilio
22+
*
23+
*/
24+
public class ExpressionResolverUnitTest {
25+
private static final String LITERAL_EXPRESSION = "literal expression";
26+
private static final String SPEL_EXPRESSION = "#{@environment.getProperty('dynamic.collection.name')}";
27+
28+
@Test
29+
public void testLiteralExpressionsShouldNotBeAltered() {
30+
assertEquals(LITERAL_EXPRESSION, ExpressionResolver.resolveExpression(LITERAL_EXPRESSION));
31+
}
32+
33+
@Test
34+
public void testExpressionsShouldBeResolved() {
35+
final AnnotationConfigApplicationContext applicationContext =
36+
new AnnotationConfigApplicationContext(TestConfiguration.class);
37+
38+
assertNotNull(applicationContext.getBean(ExpressionResolver.class));
39+
assertEquals(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME,
40+
ExpressionResolver.resolveExpression(SPEL_EXPRESSION));
41+
}
42+
43+
@Configuration
44+
@PropertySource("application.properties")
45+
static class TestConfiguration {
46+
@Bean
47+
public ExpressionResolver expressionResolver(ConfigurableBeanFactory beanFactory) {
48+
return new ExpressionResolver((ConfigurableBeanFactory) beanFactory);
49+
}
50+
}
51+
52+
}

src/test/java/com/microsoft/azure/spring/data/cosmosdb/common/TestConstants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,5 +151,9 @@ public class TestConstants {
151151
public static final int PAGE_SIZE_1 = 1;
152152
public static final int PAGE_SIZE_2 = 2;
153153
public static final int PAGE_SIZE_3 = 3;
154+
155+
public static final String DYNAMIC_PROPERTY_COLLECTION_NAME = "spel-property-collection";
156+
public static final String DYNAMIC_BEAN_COLLECTION_NAME = "spel-bean-collection";
157+
154158
}
155159

src/test/java/com/microsoft/azure/spring/data/cosmosdb/config/AbstractDocumentDbConfigurationIT.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212
import com.microsoft.azure.documentdb.RequestOptions;
1313
import com.microsoft.azure.spring.data.cosmosdb.Constants;
1414
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory;
15+
import com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver;
1516
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
1617
import org.assertj.core.api.Assertions;
1718
import org.junit.Rule;
1819
import org.junit.Test;
1920
import org.junit.rules.ExpectedException;
2021
import org.mockito.Mock;
22+
2123
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2224
import org.springframework.beans.factory.annotation.Value;
2325
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -26,11 +28,21 @@
2628
import org.springframework.context.annotation.PropertySource;
2729
import org.springframework.context.support.AbstractApplicationContext;
2830

31+
import static org.junit.Assert.assertNotNull;
32+
2933
public class AbstractDocumentDbConfigurationIT {
3034
private static final String OBJECTMAPPER_BEAN_NAME = Constants.OBJECTMAPPER_BEAN_NAME;
3135

3236
@Rule
3337
public ExpectedException exception = ExpectedException.none();
38+
39+
@Test
40+
public void containsExpressionResolver() {
41+
final AbstractApplicationContext context = new AnnotationConfigApplicationContext(
42+
TestDocumentDbConfiguration.class);
43+
44+
assertNotNull(context.getBean(ExpressionResolver.class));
45+
}
3446

3547
@Test
3648
public void containsDocumentDbFactory() {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
7+
package com.microsoft.azure.spring.data.cosmosdb.domain;
8+
9+
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
10+
import lombok.AllArgsConstructor;
11+
import lombok.Data;
12+
import lombok.NoArgsConstructor;
13+
14+
@Data
15+
@AllArgsConstructor
16+
@NoArgsConstructor
17+
@Document(collection = "#{@dynamicCollectionContainer.getCollectionName()}")
18+
public class SpELBeanStudent {
19+
private String id;
20+
private String firstName;
21+
private String lastName;
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
7+
package com.microsoft.azure.spring.data.cosmosdb.domain;
8+
9+
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
10+
import lombok.AllArgsConstructor;
11+
import lombok.Data;
12+
import lombok.NoArgsConstructor;
13+
14+
@Data
15+
@AllArgsConstructor
16+
@NoArgsConstructor
17+
@Document(collection = "${dynamic.collection.name}")
18+
public class SpELPropertyStudent {
19+
private String id;
20+
private String firstName;
21+
private String lastName;
22+
}

src/test/java/com/microsoft/azure/spring/data/cosmosdb/repository/TestRepositoryConfig.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,21 @@ public DocumentDBConfig getConfig() {
5454

5555
return DocumentDBConfig.builder(connectionString, dbName).requestOptions(options).build();
5656
}
57+
58+
@Bean
59+
public DynamicCollectionContainer dynamicCollectionContainer() {
60+
return new DynamicCollectionContainer("spel-bean-collection");
61+
}
62+
63+
public class DynamicCollectionContainer {
64+
private String collectionName;
65+
66+
public DynamicCollectionContainer(String collectionName) {
67+
this.collectionName = collectionName;
68+
}
69+
70+
public String getCollectionName() {
71+
return this.collectionName;
72+
}
73+
}
5774
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See LICENSE in the project root for
4+
* license information.
5+
*/
6+
package com.microsoft.azure.spring.data.cosmosdb.repository.integration;
7+
8+
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
10+
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
11+
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
12+
import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate;
13+
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
14+
import com.microsoft.azure.spring.data.cosmosdb.core.convert.ObjectMapperFactory;
15+
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
16+
import com.microsoft.azure.spring.data.cosmosdb.domain.SpELBeanStudent;
17+
import com.microsoft.azure.spring.data.cosmosdb.domain.SpELPropertyStudent;
18+
import com.microsoft.azure.spring.data.cosmosdb.repository.TestRepositoryConfig;
19+
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
20+
import org.junit.After;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.beans.factory.annotation.Value;
26+
import org.springframework.boot.autoconfigure.domain.EntityScanner;
27+
import org.springframework.context.ApplicationContext;
28+
import org.springframework.data.annotation.Persistent;
29+
import org.springframework.test.context.ContextConfiguration;
30+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
31+
32+
import static org.junit.Assert.assertEquals;
33+
import static org.junit.Assert.assertNotNull;
34+
35+
/**
36+
*
37+
* @author Domenico Sibilio
38+
*
39+
*/
40+
@RunWith(SpringJUnit4ClassRunner.class)
41+
@ContextConfiguration(classes = TestRepositoryConfig.class)
42+
public class SpELDocumentDBAnnotationIT {
43+
private static final SpELPropertyStudent TEST_PROPERTY_STUDENT =
44+
new SpELPropertyStudent(TestConstants.ID_1, TestConstants.FIRST_NAME,
45+
TestConstants.LAST_NAME);
46+
47+
@Value("${cosmosdb.uri}")
48+
private String dbUri;
49+
50+
@Value("${cosmosdb.key}")
51+
private String dbKey;
52+
53+
@Autowired
54+
private ApplicationContext applicationContext;
55+
56+
private DocumentDbTemplate dbTemplate;
57+
private DocumentDbEntityInformation<SpELPropertyStudent, String> documentDbEntityInfo;
58+
59+
@After
60+
public void cleanUp() {
61+
if (dbTemplate != null && documentDbEntityInfo != null) {
62+
dbTemplate.deleteCollection(documentDbEntityInfo.getCollectionName());
63+
}
64+
}
65+
66+
@Test
67+
public void testDynamicCollectionNameWithPropertySourceExpression() {
68+
final DocumentDbEntityInformation<SpELPropertyStudent, Object> propertyStudentInfo =
69+
new DocumentDbEntityInformation<>(SpELPropertyStudent.class);
70+
71+
assertEquals(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME, propertyStudentInfo.getCollectionName());
72+
}
73+
74+
@Test
75+
public void testDynamicCollectionNameWithBeanExpression() {
76+
final DocumentDbEntityInformation<SpELBeanStudent, Object> beanStudentInfo =
77+
new DocumentDbEntityInformation<>(SpELBeanStudent.class);
78+
79+
assertEquals(TestConstants.DYNAMIC_BEAN_COLLECTION_NAME, beanStudentInfo.getCollectionName());
80+
}
81+
82+
@Test
83+
public void testDatabaseOperationsOnDynamicallyNamedCollection() throws ClassNotFoundException {
84+
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(dbUri, dbKey, TestConstants.DB_NAME).build();
85+
final CosmosDbFactory dbFactory = new CosmosDbFactory(dbConfig);
86+
87+
documentDbEntityInfo = new DocumentDbEntityInformation<>(SpELPropertyStudent.class);
88+
final DocumentDbMappingContext dbContext = new DocumentDbMappingContext();
89+
dbContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class));
90+
91+
final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
92+
final MappingDocumentDbConverter mappingConverter = new MappingDocumentDbConverter(dbContext, objectMapper);
93+
dbTemplate = new DocumentDbTemplate(dbFactory, mappingConverter, TestConstants.DB_NAME);
94+
95+
dbTemplate.createCollectionIfNotExists(documentDbEntityInfo);
96+
97+
final SpELPropertyStudent insertedRecord =
98+
dbTemplate.insert(documentDbEntityInfo.getCollectionName(), TEST_PROPERTY_STUDENT, null);
99+
assertNotNull(insertedRecord);
100+
101+
final SpELPropertyStudent readRecord =
102+
dbTemplate.findById(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME,
103+
insertedRecord.getId(), SpELPropertyStudent.class);
104+
assertNotNull(readRecord);
105+
}
106+
107+
}
108+

0 commit comments

Comments
 (0)