From e7f93b9e0bfb8c17b72697d306029b0b0109577e Mon Sep 17 00:00:00 2001 From: Yaron Yamin Date: Tue, 6 Dec 2016 00:25:27 +0200 Subject: [PATCH] INT-4173 Add Query support for Mongo i-c-adapter JIRA: https://jira.spring.io/browse/INT-4173 INT-4173 convert query-expression to BasicQuery - fix checkstyle violation INT-4173 convert query-expression to BasicQuery - refactor. address CR comments INT-4173 convert query-expression to BasicQuery - UT coverage, xsd attribute documentation INT-4173 cr comments, refactoring, register mongo api package for int-mongo:inbound-channel-adapter query-expression INT-4173: Documentation, address PR comments & checkstyle violations Polishing code style and imports order **Cherry-pick to 4.3.x** Conflicts: spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSourceTests.java --- .../mongodb/inbound/MongoDbMessageSource.java | 35 +++++++--- .../config/spring-integration-mongodb-4.3.xsd | 15 ++--- ...ChannelAdapterIntegrationTests-context.xml | 19 ++++++ ...InboundChannelAdapterIntegrationTests.java | 33 ++++++++++ ...boundChannelAdapterParserTests-context.xml | 37 +++++++++-- ...ngoDbInboundChannelAdapterParserTests.java | 65 ++++++++++++++----- .../inbound/MongoDbMessageSourceTests.java | 47 ++++++++++---- src/reference/asciidoc/mongodb.adoc | 5 +- 8 files changed, 201 insertions(+), 55 deletions(-) diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java index ce2024c8a6..973fa49eb1 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/inbound/MongoDbMessageSource.java @@ -25,8 +25,10 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.expression.Expression; +import org.springframework.expression.TypeLocator; import org.springframework.expression.common.LiteralExpression; import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.integration.context.IntegrationObjectSupport; import org.springframework.integration.core.MessageSource; import org.springframework.integration.expression.ExpressionUtils; @@ -55,11 +57,12 @@ import com.mongodb.DBObject; * * @author Amol Nayak * @author Oleg Zhurakousky + * @author Yaron Yamin * * @since 2.2 */ public class MongoDbMessageSource extends IntegrationObjectSupport - implements MessageSource { + implements MessageSource { private final Expression queryExpression; @@ -84,7 +87,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * which should resolve to a MongoDb 'query' string * (see http://www.mongodb.org/display/DOCS/Querying). * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * * @param mongoDbFactory The mongodb factory. * @param queryExpression The query expression. */ @@ -102,7 +104,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * (see http://www.mongodb.org/display/DOCS/Querying). * It assumes that the {@link MongoOperations} is fully initialized and ready to be used. * The 'queryExpression' will be evaluated on every call to the {@link #receive()} method. - * * @param mongoTemplate The mongo template. * @param queryExpression The query expression. */ @@ -119,7 +120,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * {@link MongoTemplate#find(Query, Class)} or {@link MongoTemplate#findOne(Query, Class)} * method. * Default is {@link DBObject}. - * * @param entityClass The entity class. */ public void setEntityClass(Class entityClass) { @@ -134,7 +134,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * {@link #receive()} will use {@link MongoTemplate#findOne(Query, Class)}, * and the payload of the returned {@link Message} will be the returned target Object of type * identified by {{@link #entityClass} instead of a List. - * * @param expectSingleResult true if a single result is expected. */ public void setExpectSingleResult(boolean expectSingleResult) { @@ -145,7 +144,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * Sets the SpEL {@link Expression} that should resolve to a collection name * used by the {@link Query}. The resulting collection name will be included * in the {@link MongoHeaders#COLLECTION_NAME} header. - * * @param collectionNameExpression The collection name expression. */ public void setCollectionNameExpression(Expression collectionNameExpression) { @@ -157,7 +155,6 @@ public class MongoDbMessageSource extends IntegrationObjectSupport * Allows you to provide a custom {@link MongoConverter} used to assist in deserialization * data read from MongoDb. Only allowed if this instance was constructed with a * {@link MongoDbFactory}. - * * @param mongoConverter The mongo converter. */ public void setMongoConverter(MongoConverter mongoConverter) { @@ -174,8 +171,12 @@ public class MongoDbMessageSource extends IntegrationObjectSupport @Override protected void onInit() throws Exception { this.evaluationContext = - ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); - + ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); + TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); + if (typeLocator instanceof StandardTypeLocator) { + //Register MongoDB query API package so FQCN can be avoided in query-expression. + ((StandardTypeLocator) typeLocator).registerImport("org.springframework.data.mongodb.core.query"); + } if (this.mongoTemplate == null) { this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mongoConverter); } @@ -194,7 +195,20 @@ public class MongoDbMessageSource extends IntegrationObjectSupport public Message receive() { Assert.isTrue(this.initialized, "This class is not yet initialized. Invoke its afterPropertiesSet() method"); Message message = null; - Query query = new BasicQuery(this.queryExpression.getValue(this.evaluationContext, String.class)); + Object value = this.queryExpression.getValue(this.evaluationContext); + Assert.notNull(value, "'queryExpression' must not evaluate to null"); + Query query; + if (value instanceof String) { + query = new BasicQuery((String) value); + } + else if (value instanceof Query) { + query = ((Query) value); + } + else { + throw new IllegalStateException("'queryExpression' must evaluate to String " + + "or org.springframework.data.mongodb.core.query.Query"); + } + Assert.notNull(query, "'queryExpression' must not evaluate to null"); String collectionName = this.collectionNameExpression.getValue(this.evaluationContext, String.class); Assert.notNull(collectionName, "'collectionNameExpression' must not evaluate to null"); @@ -225,4 +239,5 @@ public class MongoDbMessageSource extends IntegrationObjectSupport return message; } + } diff --git a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-4.3.xsd b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-4.3.xsd index 1ce5ee8235..fbee90169d 100644 --- a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-4.3.xsd +++ b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-4.3.xsd @@ -1,10 +1,10 @@ + xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:tool="http://www.springframework.org/schema/tool" + xmlns:integration="http://www.springframework.org/schema/integration" + targetNamespace="http://www.springframework.org/schema/integration/mongodb" + elementFormDefault="qualified" attributeFormDefault="unqualified"> - SpEL expression which should resolve to a - String value identifying the - MongoDb Query. Also see 'query' attribute + SpEL expression which should resolve to a String query (please refer to the 'query' attribute), + or to an instance of MongoDb Query (e.q., query-expression="new BasicQuery('{''name'' : ''Bob''}').limit(2)"). This attribute is mutually exclusive with 'query' attribute. diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml index 7c8b331424..4ba01ccca2 100644 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterIntegrationTests-context.xml @@ -48,6 +48,25 @@ auto-startup="false"> + + + + + + + > message = (Message>) replyChannel.receive(10000); + assertNotNull(message); + assertEquals(1, message.getPayload().size()); + assertEquals("Bob", message.getPayload().get(0).getName()); + this.mongoInboundAdapterWithQueryExpression.stop(); + } + @Test + @MongoDbAvailable + public void testWithStringQueryExpression() throws Exception { + this.mongoTemplate.save(this.createPerson("Bob"), "foo"); + this.mongoInboundAdapterWithStringQueryExpression.start(); + @SuppressWarnings("unchecked") + Message> message = (Message>) replyChannel.receive(10000); + assertNotNull(message); + assertEquals("Bob", message.getPayload().get(0).getName()); + this.mongoInboundAdapterWithStringQueryExpression.stop(); + } @Test @MongoDbAvailable diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml index 4e0d835339..b1b3eaae16 100644 --- a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbInboundChannelAdapterParserTests-context.xml @@ -1,11 +1,9 @@ @@ -28,6 +26,33 @@ + + + + + + + + + + + + persons = queryMultipleElements(new LiteralExpression("{'address.state' : 'PA'}")); + assertEquals(3, persons.size()); + } + @Test + @MongoDbAvailable + public void validateSuccessfulStringQueryExpressionWithMultipleElements() throws Exception { + List persons = queryMultipleElements(new SpelExpressionParser() + .parseExpression("\"{'address.state' : 'PA'}\"")); + assertEquals(3, persons.size()); + } + + @Test + @MongoDbAvailable + public void validateSuccessfulBasicQueryExpressionWithMultipleElements() throws Exception { + List persons = queryMultipleElements(new SpelExpressionParser() + .parseExpression("new BasicQuery(\"{'address.state' : 'PA'}\").limit(2)")); + assertEquals(2, persons.size()); + } + + @SuppressWarnings("unchecked") + private List queryMultipleElements(Expression queryExpression) throws Exception { MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); MongoTemplate template = new MongoTemplate(mongoDbFactory); @@ -170,18 +194,16 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests { template.save(this.createPerson("Moe"), "data"); template.save(this.createPerson("Jack"), "data"); - Expression queryExpression = new LiteralExpression("{'address.state' : 'PA'}"); MongoDbMessageSource messageSource = new MongoDbMessageSource(mongoDbFactory, queryExpression); messageSource.setBeanFactory(mock(BeanFactory.class)); messageSource.afterPropertiesSet(); - @SuppressWarnings("unchecked") - List persons = (List) messageSource.receive().getPayload(); - assertEquals(3, persons.size()); + + return (List) messageSource.receive().getPayload(); } @Test @MongoDbAvailable - public void validateSuccessfullQueryWithNullReturn() throws Exception { + public void validateSuccessfulQueryWithNullReturn() throws Exception { MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); @@ -200,7 +222,7 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests { @SuppressWarnings("unchecked") @Test @MongoDbAvailable - public void validateSuccessfullQueryWithCustomConverter() throws Exception { + public void validateSuccessfulQueryWithCustomConverter() throws Exception { MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); @@ -226,7 +248,7 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests { @SuppressWarnings("unchecked") @Test @MongoDbAvailable - public void validateSuccessfullQueryWithMongoTemplate() throws Exception { + public void validateSuccessfulQueryWithMongoTemplate() throws Exception { MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); @@ -272,4 +294,5 @@ public class MongoDbMessageSourceTests extends MongoDbAvailableTests { result = (DBObject) messageSource.receive().getPayload(); assertEquals(id, result.get("_id")); } + } diff --git a/src/reference/asciidoc/mongodb.adoc b/src/reference/asciidoc/mongodb.adoc index 671e4c5fca..06637b837c 100644 --- a/src/reference/asciidoc/mongodb.adoc +++ b/src/reference/asciidoc/mongodb.adoc @@ -190,7 +190,8 @@ The _MongoDb Inbound Channel Adapter_ is a polling consumer that reads data from As you can see from the configuration above, you configure a _MongoDb Inbound Channel Adapter_ using the `inbound-channel-adapter` element, providing values for various attributes such as: -* `query` or `query-expression` - a JSON query (see http://www.mongodb.org/display/DOCS/Querying[MongoDb Querying]) +* `query` - a JSON query (see http://www.mongodb.org/display/DOCS/Querying[MongoDb Querying]) +* `query-expression` - A SpEL expression that is evaluated to a JSON query String (as the `query` attribute above), or to an instance of `o.s.data.mongodb.core.query.Query`. Mutually exclusive with `query` attribute. * `entity-class` - the type of the payload object; if not supplied, a `com.mongodb.DBObject` will be returned. * `collection-name` or `collection-name-expression` - Identifies the name of the MongoDb collection to use. * `mongodb-factory` - reference to an instance of `o.s.data.mongodb.MongoDbFactory` @@ -214,7 +215,7 @@ You can do this using Transaction Synchronization feature that was added with Sp ----