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
This commit is contained in:
Родитель
b500a9ac7f
Коммит
a1f7dd3116
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.common;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.EmbeddedValueResolver;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
public class ExpressionResolver {
|
||||
|
||||
private static EmbeddedValueResolver embeddedValueResolver;
|
||||
|
||||
public ExpressionResolver(BeanFactory beanFactory) {
|
||||
if (beanFactory instanceof ConfigurableBeanFactory) {
|
||||
setEmbeddedValueResolver(new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given string value via an {@link EmbeddedValueResolver}
|
||||
* @param expression the expression to be resolved
|
||||
* @return the resolved expression, may be {@literal null}
|
||||
*/
|
||||
public static String resolveExpression(String expression) {
|
||||
return embeddedValueResolver != null
|
||||
? embeddedValueResolver.resolveStringValue(expression)
|
||||
: expression;
|
||||
}
|
||||
|
||||
private static void setEmbeddedValueResolver(EmbeddedValueResolver embeddedValueResolver) {
|
||||
ExpressionResolver.embeddedValueResolver = embeddedValueResolver;
|
||||
}
|
||||
|
||||
}
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.config;
|
||||
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
|
||||
|
@ -21,10 +24,19 @@ import org.springframework.util.Assert;
|
|||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class DocumentDbConfigurationSupport {
|
||||
|
||||
@Bean
|
||||
public ExpressionResolver expressionResolver(BeanFactory beanFactory) {
|
||||
return new ExpressionResolver(beanFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DocumentDbMappingContext documentDbMappingContext() throws ClassNotFoundException {
|
||||
final DocumentDbMappingContext mappingContext = new DocumentDbMappingContext();
|
||||
|
|
|
@ -15,11 +15,14 @@ import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
|
|||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentIndexingPolicy;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.repository.core.support.AbstractEntityInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver.resolveExpression;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -135,7 +138,7 @@ public class DocumentDbEntityInformation<T, ID> extends AbstractEntityInformatio
|
|||
final Document annotation = domainClass.getAnnotation(Document.class);
|
||||
|
||||
if (annotation != null && annotation.collection() != null && !annotation.collection().isEmpty()) {
|
||||
customCollectionName = annotation.collection();
|
||||
customCollectionName = resolveExpression(annotation.collection());
|
||||
}
|
||||
|
||||
return customCollectionName;
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package com.microsoft.azure.spring.data.cosmosdb.common;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
public class ExpressionResolverUnitTest {
|
||||
private static final String LITERAL_EXPRESSION = "literal expression";
|
||||
private static final String SPEL_EXPRESSION = "#{@environment.getProperty('dynamic.collection.name')}";
|
||||
|
||||
@Test
|
||||
public void testLiteralExpressionsShouldNotBeAltered() {
|
||||
assertEquals(LITERAL_EXPRESSION, ExpressionResolver.resolveExpression(LITERAL_EXPRESSION));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpressionsShouldBeResolved() {
|
||||
final AnnotationConfigApplicationContext applicationContext =
|
||||
new AnnotationConfigApplicationContext(TestConfiguration.class);
|
||||
|
||||
assertNotNull(applicationContext.getBean(ExpressionResolver.class));
|
||||
assertEquals(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME,
|
||||
ExpressionResolver.resolveExpression(SPEL_EXPRESSION));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@PropertySource("application.properties")
|
||||
static class TestConfiguration {
|
||||
@Bean
|
||||
public ExpressionResolver expressionResolver(ConfigurableBeanFactory beanFactory) {
|
||||
return new ExpressionResolver((ConfigurableBeanFactory) beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -151,5 +151,9 @@ public class TestConstants {
|
|||
public static final int PAGE_SIZE_1 = 1;
|
||||
public static final int PAGE_SIZE_2 = 2;
|
||||
public static final int PAGE_SIZE_3 = 3;
|
||||
|
||||
public static final String DYNAMIC_PROPERTY_COLLECTION_NAME = "spel-property-collection";
|
||||
public static final String DYNAMIC_BEAN_COLLECTION_NAME = "spel-bean-collection";
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,14 @@ import com.microsoft.azure.documentdb.DocumentClient;
|
|||
import com.microsoft.azure.documentdb.RequestOptions;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.Constants;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.DocumentDbFactory;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.ExpressionResolver;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
@ -26,11 +28,21 @@ import org.springframework.context.annotation.Configuration;
|
|||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.context.support.AbstractApplicationContext;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class AbstractDocumentDbConfigurationIT {
|
||||
private static final String OBJECTMAPPER_BEAN_NAME = Constants.OBJECTMAPPER_BEAN_NAME;
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void containsExpressionResolver() {
|
||||
final AbstractApplicationContext context = new AnnotationConfigApplicationContext(
|
||||
TestDocumentDbConfiguration.class);
|
||||
|
||||
assertNotNull(context.getBean(ExpressionResolver.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void containsDocumentDbFactory() {
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.domain;
|
||||
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document(collection = "#{@dynamicCollectionContainer.getCollectionName()}")
|
||||
public class SpELBeanStudent {
|
||||
private String id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
|
||||
package com.microsoft.azure.spring.data.cosmosdb.domain;
|
||||
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Document(collection = "${dynamic.collection.name}")
|
||||
public class SpELPropertyStudent {
|
||||
private String id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
}
|
|
@ -54,4 +54,21 @@ public class TestRepositoryConfig extends AbstractDocumentDbConfiguration {
|
|||
|
||||
return DocumentDBConfig.builder(connectionString, dbName).requestOptions(options).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DynamicCollectionContainer dynamicCollectionContainer() {
|
||||
return new DynamicCollectionContainer("spel-bean-collection");
|
||||
}
|
||||
|
||||
public class DynamicCollectionContainer {
|
||||
private String collectionName;
|
||||
|
||||
public DynamicCollectionContainer(String collectionName) {
|
||||
this.collectionName = collectionName;
|
||||
}
|
||||
|
||||
public String getCollectionName() {
|
||||
return this.collectionName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE in the project root for
|
||||
* license information.
|
||||
*/
|
||||
package com.microsoft.azure.spring.data.cosmosdb.repository.integration;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.CosmosDbFactory;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.common.TestConstants;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.config.DocumentDBConfig;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.DocumentDbTemplate;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.convert.MappingDocumentDbConverter;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.convert.ObjectMapperFactory;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.DocumentDbMappingContext;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.domain.SpELBeanStudent;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.domain.SpELPropertyStudent;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.repository.TestRepositoryConfig;
|
||||
import com.microsoft.azure.spring.data.cosmosdb.repository.support.DocumentDbEntityInformation;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScanner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.data.annotation.Persistent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Domenico Sibilio
|
||||
*
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = TestRepositoryConfig.class)
|
||||
public class SpELDocumentDBAnnotationIT {
|
||||
private static final SpELPropertyStudent TEST_PROPERTY_STUDENT =
|
||||
new SpELPropertyStudent(TestConstants.ID_1, TestConstants.FIRST_NAME,
|
||||
TestConstants.LAST_NAME);
|
||||
|
||||
@Value("${cosmosdb.uri}")
|
||||
private String dbUri;
|
||||
|
||||
@Value("${cosmosdb.key}")
|
||||
private String dbKey;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
private DocumentDbTemplate dbTemplate;
|
||||
private DocumentDbEntityInformation<SpELPropertyStudent, String> documentDbEntityInfo;
|
||||
|
||||
@After
|
||||
public void cleanUp() {
|
||||
if (dbTemplate != null && documentDbEntityInfo != null) {
|
||||
dbTemplate.deleteCollection(documentDbEntityInfo.getCollectionName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicCollectionNameWithPropertySourceExpression() {
|
||||
final DocumentDbEntityInformation<SpELPropertyStudent, Object> propertyStudentInfo =
|
||||
new DocumentDbEntityInformation<>(SpELPropertyStudent.class);
|
||||
|
||||
assertEquals(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME, propertyStudentInfo.getCollectionName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDynamicCollectionNameWithBeanExpression() {
|
||||
final DocumentDbEntityInformation<SpELBeanStudent, Object> beanStudentInfo =
|
||||
new DocumentDbEntityInformation<>(SpELBeanStudent.class);
|
||||
|
||||
assertEquals(TestConstants.DYNAMIC_BEAN_COLLECTION_NAME, beanStudentInfo.getCollectionName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDatabaseOperationsOnDynamicallyNamedCollection() throws ClassNotFoundException {
|
||||
final DocumentDBConfig dbConfig = DocumentDBConfig.builder(dbUri, dbKey, TestConstants.DB_NAME).build();
|
||||
final CosmosDbFactory dbFactory = new CosmosDbFactory(dbConfig);
|
||||
|
||||
documentDbEntityInfo = new DocumentDbEntityInformation<>(SpELPropertyStudent.class);
|
||||
final DocumentDbMappingContext dbContext = new DocumentDbMappingContext();
|
||||
dbContext.setInitialEntitySet(new EntityScanner(this.applicationContext).scan(Persistent.class));
|
||||
|
||||
final ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
|
||||
final MappingDocumentDbConverter mappingConverter = new MappingDocumentDbConverter(dbContext, objectMapper);
|
||||
dbTemplate = new DocumentDbTemplate(dbFactory, mappingConverter, TestConstants.DB_NAME);
|
||||
|
||||
dbTemplate.createCollectionIfNotExists(documentDbEntityInfo);
|
||||
|
||||
final SpELPropertyStudent insertedRecord =
|
||||
dbTemplate.insert(documentDbEntityInfo.getCollectionName(), TEST_PROPERTY_STUDENT, null);
|
||||
assertNotNull(insertedRecord);
|
||||
|
||||
final SpELPropertyStudent readRecord =
|
||||
dbTemplate.findById(TestConstants.DYNAMIC_PROPERTY_COLLECTION_NAME,
|
||||
insertedRecord.getId(), SpELPropertyStudent.class);
|
||||
assertNotNull(readRecord);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ cosmosdb.secondaryKey=${COSMOSDB_SECONDARY_KEY}
|
|||
#You can also use connection string instead of uri and key to connect to cosmos DB
|
||||
#cosmosdb.connection-string=${DOCUMENTDB_CONNECTION_STRING}
|
||||
|
||||
dynamic.collection.name=spel-property-collection
|
||||
# Performance test configurations
|
||||
perf.recursive.times=10
|
||||
perf.batch.size=3
|
||||
|
|
Загрузка…
Ссылка в новой задаче