From 0fa8849f7f44f70ac84a36153351a7a45ccc6f72 Mon Sep 17 00:00:00 2001 From: Xavier Padro Date: Fri, 9 Dec 2016 00:02:45 +0100 Subject: [PATCH] INT-3335: Implement MongoDb Outbound Gateway JIRA: https://jira.spring.io/browse/INT-3335 INT-3335: PR fixes INT-3335: fix code style INT-3335: Add queryExpressionString + minor fixes * Polishing. Mostly code style --- .../config/MongoDbNamespaceHandler.java | 1 + .../config/MongoDbOutboundGatewayParser.java | 70 ++++ .../integration/mongodb/dsl/MongoDb.java | 45 +++ .../dsl/MongoDbOutboundGatewaySpec.java | 90 +++++ .../integration/mongodb/dsl/package-info.java | 4 + .../outbound/MongoDbOutboundGateway.java | 173 +++++++++ .../config/spring-integration-mongodb-5.0.xsd | 137 +++++++ ...goDbOutboundGatewayParserTests-context.xml | 57 +++ .../MongoDbOutboundGatewayParserTests.java | 124 ++++++ ...gateway-fail-template-converter-config.xml | 36 ++ ...d-gateway-fail-template-factory-config.xml | 29 ++ .../integration/mongodb/dsl/MongoDbTests.java | 365 ++++++++++++++++++ .../MongoDbOutboundGatewayTests-context.xml | 22 ++ .../outbound/MongoDbOutboundGatewayTests.java | 318 +++++++++++++++ ...MongoDbOutboundGatewayXmlTests-context.xml | 82 ++++ .../MongoDbOutboundGatewayXmlTests.java | 167 ++++++++ src/reference/asciidoc/mongodb.adoc | 149 ++++++- src/reference/asciidoc/whats-new.adoc | 4 + 18 files changed, 1857 insertions(+), 16 deletions(-) create mode 100644 spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java create mode 100644 spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java create mode 100644 spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java create mode 100644 spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java create mode 100644 spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml create mode 100644 spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java index 0f6fbc3d53..6346d686b8 100644 --- a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbNamespaceHandler.java @@ -31,5 +31,6 @@ public class MongoDbNamespaceHandler extends AbstractIntegrationNamespaceHandler public void init() { registerBeanDefinitionParser("inbound-channel-adapter", new MongoDbInboundChannelAdapterParser()); registerBeanDefinitionParser("outbound-channel-adapter", new MongoDbOutboundChannelAdapterParser()); + registerBeanDefinitionParser("outbound-gateway", new MongoDbOutboundGatewayParser()); } } diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java new file mode 100644 index 0000000000..16c080bc29 --- /dev/null +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParser.java @@ -0,0 +1,70 @@ +/* + * 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.integration.mongodb.config; + +import org.w3c.dom.Element; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractConsumerEndpointParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; +import org.springframework.util.StringUtils; + +/** + * Parser for MongoDb outbound gateways + * + * @author Xavier Padró + * @since 5.0 + */ +public class MongoDbOutboundGatewayParser extends AbstractConsumerEndpointParser { + + @Override + protected BeanDefinitionBuilder parseHandler(Element element, ParserContext parserContext) { + final BeanDefinitionBuilder builder = + BeanDefinitionBuilder.genericBeanDefinition(MongoDbOutboundGateway.class); + + MongoParserUtils.processCommonAttributes(element, parserContext, builder); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "reply-timeout"); + String replyChannel = element.getAttribute("reply-channel"); + + if (StringUtils.hasText(replyChannel)) { + builder.addPropertyReference("outputChannel", replyChannel); + } + + BeanDefinition queryExpressionDef = + IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression("query", + "query-expression", parserContext, element, true); + + if (queryExpressionDef != null) { + builder.addPropertyValue("queryExpression", queryExpressionDef); + } + + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "expect-single-result"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(builder, element, "entity-class"); + + return builder; + } + + @Override + protected String getInputChannelAttributeName() { + return "request-channel"; + } + +} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java new file mode 100644 index 0000000000..3007c3a4a1 --- /dev/null +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDb.java @@ -0,0 +1,45 @@ +/* + * 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.integration.mongodb.dsl; + +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MongoConverter; + +/** + * Factory class for building MongoDb components + * + * @author Xavier Padró + * @since 5.0 + */ +public final class MongoDb { + + public static MongoDbOutboundGatewaySpec outboundGateway( + MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { + + return new MongoDbOutboundGatewaySpec(mongoDbFactory, mongoConverter); + } + + public static MongoDbOutboundGatewaySpec outboundGateway(MongoOperations mongoTemplate) { + return new MongoDbOutboundGatewaySpec(mongoTemplate); + } + + private MongoDb() { + super(); + } + +} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java new file mode 100644 index 0000000000..074916009d --- /dev/null +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/MongoDbOutboundGatewaySpec.java @@ -0,0 +1,90 @@ +/* + * 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.integration.mongodb.dsl; + +import java.util.function.Function; + +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.integration.dsl.MessageHandlerSpec; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; +import org.springframework.messaging.Message; + +/** + * A {@link MessageHandlerSpec} extension for the MongoDb Outbound endpoint {@link MongoDbOutboundGateway} + * + * @author Xavier Padró + * @since 5.0 + */ +public class MongoDbOutboundGatewaySpec + extends MessageHandlerSpec { + + MongoDbOutboundGatewaySpec(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { + this.target = new MongoDbOutboundGateway(mongoDbFactory, mongoConverter); + this.target.setRequiresReply(true); + } + + MongoDbOutboundGatewaySpec(MongoOperations mongoTemplate) { + this.target = new MongoDbOutboundGateway(mongoTemplate); + this.target.setRequiresReply(true); + } + + public MongoDbOutboundGatewaySpec expectSingleResult(boolean expectSingleResult) { + this.target.setExpectSingleResult(expectSingleResult); + return this; + } + + public MongoDbOutboundGatewaySpec query(String query) { + this.target.setQueryExpression(new LiteralExpression(query)); + return this; + } + + public MongoDbOutboundGatewaySpec queryExpression(String queryExpression) { + this.target.setQueryExpressionString(queryExpression); + return this; + } + + public

MongoDbOutboundGatewaySpec queryFunction(Function, Query> queryFunction) { + this.target.setQueryExpression(new FunctionExpression<>(queryFunction)); + return this; + } + + public MongoDbOutboundGatewaySpec entityClass(Class entityClass) { + this.target.setEntityClass(entityClass); + return this; + } + + public MongoDbOutboundGatewaySpec collectionName(String collectionName) { + this.target.setCollectionNameExpression(new LiteralExpression(collectionName)); + return this; + } + + public MongoDbOutboundGatewaySpec collectionNameExpression(String collectionNameExpression) { + this.target.setCollectionNameExpressionString(collectionNameExpression); + return this; + } + + public

MongoDbOutboundGatewaySpec collectionNameFunction(Function, String> collectionNameFunction) { + this.target.setCollectionNameExpression(new FunctionExpression<>(collectionNameFunction)); + return this; + } + +} diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java new file mode 100644 index 0000000000..395197955d --- /dev/null +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/dsl/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides MongoDB Components support for Java DSL. + */ +package org.springframework.integration.mongodb.dsl; diff --git a/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java new file mode 100644 index 0000000000..048339c3d5 --- /dev/null +++ b/spring-integration-mongodb/src/main/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGateway.java @@ -0,0 +1,173 @@ +/* + * 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.integration.mongodb.outbound; + +import org.bson.Document; + +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.spel.support.StandardTypeLocator; +import org.springframework.integration.expression.ExpressionUtils; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.messaging.Message; +import org.springframework.util.Assert; + +/** + * Makes outbound operations to query a MongoDb database using a {@link MongoOperations} + * + * @author Xavier Padró + * @since 5.0 + */ +public class MongoDbOutboundGateway extends AbstractReplyProducingMessageHandler { + + private MongoDbFactory mongoDbFactory; + + private MongoConverter mongoConverter; + + private MongoOperations mongoTemplate; + + private EvaluationContext evaluationContext; + + private Expression queryExpression; + + private boolean expectSingleResult = false; + + private Class entityClass = Document.class; + + private Expression collectionNameExpression; + + public MongoDbOutboundGateway(MongoDbFactory mongoDbFactory) { + this(mongoDbFactory, new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), + new MongoMappingContext())); + } + + public MongoDbOutboundGateway(MongoDbFactory mongoDbFactory, MongoConverter mongoConverter) { + Assert.notNull(mongoDbFactory, "mongoDbFactory must not be null."); + Assert.notNull(mongoConverter, "mongoConverter must not be null."); + this.mongoDbFactory = mongoDbFactory; + this.mongoConverter = mongoConverter; + } + + public MongoDbOutboundGateway(MongoOperations mongoTemplate) { + Assert.notNull(mongoTemplate, "mongoTemplate must not be null."); + this.mongoTemplate = mongoTemplate; + } + + public void setQueryExpression(Expression queryExpression) { + Assert.notNull(queryExpression, "queryExpression must not be null."); + this.queryExpression = queryExpression; + } + + public void setQueryExpressionString(String queryExpressionString) { + Assert.notNull(queryExpressionString, "queryExpressionString must not be null."); + this.queryExpression = EXPRESSION_PARSER.parseExpression(queryExpressionString); + } + + public void setExpectSingleResult(boolean expectSingleResult) { + this.expectSingleResult = expectSingleResult; + } + + public void setEntityClass(Class entityClass) { + Assert.notNull(entityClass, "entityClass must not be null."); + this.entityClass = entityClass; + } + + public void setCollectionNameExpression(Expression collectionNameExpression) { + Assert.notNull(collectionNameExpression, "collectionNameExpression must not be null."); + this.collectionNameExpression = collectionNameExpression; + } + + public void setCollectionNameExpressionString(String collectionNameExpressionString) { + Assert.notNull(collectionNameExpressionString, "collectionNameExpressionString must not be null."); + this.collectionNameExpression = EXPRESSION_PARSER.parseExpression(collectionNameExpressionString); + } + + public void setMongoConverter(MongoConverter mongoConverter) { + Assert.notNull(mongoConverter, "mongoConverter cannot be null"); + Assert.isNull(this.mongoTemplate, + "'mongoConverter' can not be set when instance was constructed with MongoTemplate"); + this.mongoConverter = mongoConverter; + } + + @Override + protected void doInit() { + Assert.state(this.queryExpression != null, "no query specified"); + Assert.state(this.collectionNameExpression != null, "no collection name specified"); + + if (this.evaluationContext == null) { + this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.getBeanFactory()); + + TypeLocator typeLocator = this.evaluationContext.getTypeLocator(); + if (typeLocator instanceof StandardTypeLocator) { + ((StandardTypeLocator) typeLocator).registerImport(Query.class.getPackage().getName()); + } + } + + if (this.mongoTemplate == null) { + this.mongoTemplate = new MongoTemplate(this.mongoDbFactory, this.mongoConverter); + } + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + String collectionName = + this.collectionNameExpression.getValue(this.evaluationContext, requestMessage, String.class); + Query query = buildQuery(requestMessage); + + Object result; + + if (this.expectSingleResult) { + result = this.mongoTemplate.findOne(query, this.entityClass, collectionName); + } + else { + result = this.mongoTemplate.find(query, this.entityClass, collectionName); + } + + return result; + } + + private Query buildQuery(Message requestMessage) { + Query query; + + Object expressionValue = + this.queryExpression.getValue(this.evaluationContext, requestMessage, Object.class); + + if (expressionValue instanceof String) { + query = new BasicQuery((String) expressionValue); + } + else if (expressionValue instanceof Query) { + query = ((Query) expressionValue); + } + else { + throw new IllegalStateException("'queryExpression' must evaluate to " + + "String or org.springframework.data.mongodb.core.query.Query"); + } + + return query; + } + +} diff --git a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-5.0.xsd b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-5.0.xsd index 0b17920ce9..34fa7a96a7 100644 --- a/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-5.0.xsd +++ b/spring-integration-mongodb/src/main/resources/org/springframework/integration/mongodb/config/spring-integration-mongodb-5.0.xsd @@ -102,6 +102,143 @@ + + + + Configures a Consumer Endpoint for the + 'org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway' for + querying a MongoDb database in response to a message on the request channel. + + The response received from the database will be used to create the response + Message on the reply channel. + + + + + + + + + The Message Channel where messages will be sent in order + to query the database. + + + + + + + + + + + + The Message Channel to which the database response will be sent. + + + + + + + + + + + + + + + + + Specify whether this outbound gateway must return a non-null value. This value is + 'true' by default, and a ReplyRequiredException will be thrown when + the underlying service returns a null value. + + + + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to a SubscribableChannel. + + + + + + + + + + + + + + + + String representation of a MongoDb Query (e.g., + query="{'name' : 'Bob'}"). + Please refer to MongoDb documentation for more query samples + http://www.mongodb.org/display/DOCS/Querying + This attribute is + mutually exclusive with 'query-expression' 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)"). + + + + + + + + The fully qualified name of the entity class to be passed to + find(..) or findOne(..) method MongoTemplate. + If this attribute is not provided the default value is org.bson.Document + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml new file mode 100644 index 0000000000..59e143acba --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests-context.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java new file mode 100644 index 0000000000..3fae4305c5 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/MongoDbOutboundGatewayParserTests.java @@ -0,0 +1,124 @@ +/* + * 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.integration.mongodb.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.expression.common.LiteralExpression; +import org.springframework.expression.spel.standard.SpelExpression; +import org.springframework.integration.mongodb.outbound.MongoDbOutboundGateway; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Xavier Padró + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringRunner.class) +@DirtiesContext +public class MongoDbOutboundGatewayParserTests { + + @Autowired + private ApplicationContext context; + + @Autowired + private MongoDbFactory mongoDbFactory; + + @Autowired + private MongoConverter mongoConverter; + + @Test + public void minimalConfig() { + MongoDbOutboundGateway gateway = + TestUtils.getPropertyValue(context.getBean("minimalConfig"), "handler", MongoDbOutboundGateway.class); + + assertNotNull(TestUtils.getPropertyValue(gateway, "mongoTemplate")); + assertSame(this.mongoDbFactory, TestUtils.getPropertyValue(gateway, "mongoDbFactory")); + assertNotNull(TestUtils.getPropertyValue(gateway, "evaluationContext")); + assertTrue(TestUtils.getPropertyValue(gateway, "collectionNameExpression") instanceof LiteralExpression); + assertEquals("foo", TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")); + } + + @Test + public void fullConfigWithCollectionExpression() { + MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( + context.getBean("fullConfigWithCollectionExpression"), "handler", MongoDbOutboundGateway.class); + + assertNotNull(TestUtils.getPropertyValue(gateway, "mongoTemplate")); + assertSame(this.mongoDbFactory, TestUtils.getPropertyValue(gateway, "mongoDbFactory")); + assertSame(this.mongoConverter, TestUtils.getPropertyValue(gateway, "mongoConverter")); + assertNotNull(TestUtils.getPropertyValue(gateway, "evaluationContext")); + assertTrue(TestUtils.getPropertyValue(gateway, "collectionNameExpression") instanceof SpelExpression); + assertEquals("headers.collectionName", + TestUtils.getPropertyValue(gateway, "collectionNameExpression.expression")); + } + + @Test + public void fullConfigWithCollection() { + MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( + context.getBean("fullConfigWithCollection"), "handler", MongoDbOutboundGateway.class); + + assertNotNull(TestUtils.getPropertyValue(gateway, "mongoTemplate")); + assertSame(this.mongoDbFactory, TestUtils.getPropertyValue(gateway, "mongoDbFactory")); + assertSame(this.mongoConverter, TestUtils.getPropertyValue(gateway, "mongoConverter")); + assertNotNull(TestUtils.getPropertyValue(gateway, "evaluationContext")); + assertTrue(TestUtils.getPropertyValue(gateway, "collectionNameExpression") instanceof LiteralExpression); + assertEquals("foo", TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")); + } + + @Test + public void fullConfigWithMongoTemplate() { + MongoDbOutboundGateway gateway = TestUtils.getPropertyValue( + context.getBean("fullConfigWithTemplate"), "handler", MongoDbOutboundGateway.class); + + assertEquals(context.getBean("mongoDbTemplate"), TestUtils.getPropertyValue(gateway, "mongoTemplate")); + assertNull(TestUtils.getPropertyValue(gateway, "mongoDbFactory")); + assertNull(TestUtils.getPropertyValue(gateway, "mongoConverter")); + assertNotNull(TestUtils.getPropertyValue(gateway, "evaluationContext")); + assertTrue(TestUtils.getPropertyValue(gateway, "collectionNameExpression") instanceof LiteralExpression); + assertEquals("foo", TestUtils.getPropertyValue(gateway, "collectionNameExpression.literalValue")); + } + + @Test(expected = BeanDefinitionParsingException.class) + public void templateAndFactoryFail() { + new ClassPathXmlApplicationContext("outbound-gateway-fail-template-factory-config.xml", this.getClass()) + .close(); + } + + @Test(expected = BeanDefinitionParsingException.class) + public void templateAndConverterFail() { + new ClassPathXmlApplicationContext("outbound-gateway-fail-template-converter-config.xml", + this.getClass()).close(); + } + +} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml new file mode 100644 index 0000000000..9a0d1357e9 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-converter-config.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml new file mode 100644 index 0000000000..4a7db651c5 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/config/outbound-gateway-fail-template-factory-config.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java new file mode 100644 index 0000000000..ca72f5a15b --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/dsl/MongoDbTests.java @@ -0,0 +1,365 @@ +/* + * 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.integration.mongodb.dsl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.BulkOperations; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoDbFactory; +import org.springframework.data.mongodb.core.convert.MongoConverter; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.dsl.IntegrationFlow; +import org.springframework.integration.dsl.channel.MessageChannels; +import org.springframework.integration.handler.ReplyRequiredException; +import org.springframework.integration.mongodb.rules.MongoDbAvailable; +import org.springframework.integration.mongodb.rules.MongoDbAvailableTests; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import com.mongodb.MongoClient; + +/** + * @author Xavier Padró + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class MongoDbTests extends MongoDbAvailableTests { + + private static final String COLLECTION_NAME = "data"; + + @Autowired + private PollableChannel getResultChannel; + + @Autowired + @Qualifier("gatewaySingleQueryFlow.input") + private MessageChannel gatewaySingleQueryFlow; + + @Autowired + @Qualifier("gatewaySingleQueryWithTemplateFlow.input") + private MessageChannel gatewaySingleQueryWithTemplateFlow; + + @Autowired + @Qualifier("gatewaySingleQueryExpressionFlow.input") + private MessageChannel gatewaySingleQueryExpressionFlow; + + @Autowired + @Qualifier("gatewayQueryExpressionFlow.input") + private MessageChannel gatewayQueryExpressionFlow; + + @Autowired + @Qualifier("gatewayQueryExpressionLimitFlow.input") + private MessageChannel gatewayQueryExpressionLimitFlow; + + @Autowired + @Qualifier("gatewayQueryFunctionFlow.input") + private MessageChannel gatewayQueryFunctionFlow; + + @Autowired + @Qualifier("gatewayCollectionNameFunctionFlow.input") + private MessageChannel gatewayCollectionNameFunctionFlow; + + @Autowired + private MongoOperations mongoTemplate; + + @Before + public void setUp() throws Exception { + createPersons(); + } + + @After + public void cleanUp() { + mongoTemplate.dropCollection(COLLECTION_NAME); + } + + @Test + @MongoDbAvailable + public void testGatewayWithSingleQuery() { + gatewaySingleQueryFlow.send(MessageBuilder + .withPayload("Xavi") + .setHeader("collection", "data") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + Person retrievedPerson = (Person) result.getPayload(); + assertEquals("Xavi", retrievedPerson.getName()); + } + + @Test + @MongoDbAvailable + public void testGatewayWithSingleQueryWithTemplate() { + gatewaySingleQueryWithTemplateFlow.send(MessageBuilder.withPayload("Xavi").build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + Person retrievedPerson = (Person) result.getPayload(); + assertEquals("Xavi", retrievedPerson.getName()); + } + + @Test + @MongoDbAvailable + public void testGatewayWithSingleQueryExpression() { + gatewaySingleQueryExpressionFlow.send(MessageBuilder + .withPayload("") + .setHeader("query", "{'name' : 'Artem'}") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + Person retrievedPerson = (Person) result.getPayload(); + assertEquals("Artem", retrievedPerson.getName()); + } + + @Test(expected = ReplyRequiredException.class) + @MongoDbAvailable + public void testGatewayWithSingleQueryExpressionNoPersonFound() { + gatewaySingleQueryExpressionFlow.send(MessageBuilder + .withPayload("") + .setHeader("query", "{'name' : 'NonExisting'}") + .build()); + + this.getResultChannel.receive(10_000); + } + + @Test + @MongoDbAvailable + public void testGatewayWithQueryExpression() { + gatewayQueryExpressionFlow.send(MessageBuilder + .withPayload("") + .setHeader("query", "{}") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + List retrievedPersons = getPersons(result); + assertEquals(4, retrievedPersons.size()); + } + + @Test + @MongoDbAvailable + public void testGatewayWithQueryExpressionAndLimit() { + gatewayQueryExpressionLimitFlow.send(MessageBuilder + .withPayload("") + .setHeader("query", "{}") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + List retrievedPersons = getPersons(result); + assertEquals(2, retrievedPersons.size()); + } + + @Test + @MongoDbAvailable + public void testGatewayWithQueryFunction() { + gatewayQueryFunctionFlow.send(MessageBuilder + .withPayload("Gary") + .setHeader("collection", "data") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + Person person = (Person) result.getPayload(); + assertEquals("Gary", person.getName()); + } + + @Test + @MongoDbAvailable + public void testGatewayWithCollectionNameFunction() { + gatewayCollectionNameFunctionFlow.send(MessageBuilder + .withPayload("data") + .setHeader("query", "{'name' : 'Gary'}") + .build()); + + Message result = this.getResultChannel.receive(10_000); + + assertNotNull(result); + Person person = (Person) result.getPayload(); + assertEquals("Gary", person.getName()); + } + + @SuppressWarnings("unchecked") + private List getPersons(Message message) { + return (List) message.getPayload(); + } + + private void createPersons() { + BulkOperations bulkOperations = this.mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, COLLECTION_NAME); + bulkOperations.insert(Arrays.asList( + this.createPerson("Artem"), + this.createPerson("Gary"), + this.createPerson("Oleg"), + this.createPerson("Xavi"))); + bulkOperations.execute(); + } + + @Configuration + @EnableIntegration + public static class ContextConfiguration { + + @Bean + public IntegrationFlow gatewaySingleQueryFlow() { + return f -> f + .handle(queryOutboundGateway("{name: 'Xavi'}", true)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewaySingleQueryWithTemplateFlow() { + return f -> f + .handle(queryOutboundGatewayWithTemplate("{name: 'Xavi'}", true)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewaySingleQueryExpressionFlow() { + return f -> f + .handle(queryExpressionOutboundGateway(true)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewayQueryExpressionFlow() { + return f -> f + .handle(queryExpressionOutboundGateway(false)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewayQueryExpressionLimitFlow() { + return f -> f + .handle(queryExpressionOutboundGateway(false, 2)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewayQueryFunctionFlow() { + return f -> f + .handle(queryFunctionOutboundGateway(true)) + .channel(getResultChannel()); + } + + @Bean + public IntegrationFlow gatewayCollectionNameFunctionFlow() { + return f -> f + .handle(collectionNameFunctionOutboundGateway(true)) + .channel(getResultChannel()); + } + + @Bean + public MessageChannel getResultChannel() { + return MessageChannels.queue().get(); + } + + @Bean + public MongoDbFactory mongoDbFactory() { + return new SimpleMongoDbFactory(new MongoClient(), "test"); + } + + @Bean + public MongoConverter mongoConverter() { + return new TestMongoConverter(mongoDbFactory(), new MongoMappingContext()); + } + + @Bean + public MongoOperations mongoTemplate() { + return new MongoTemplate(mongoDbFactory()); + } + + private MongoDbOutboundGatewaySpec queryOutboundGateway(String query, boolean expectSingleResult) { + return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) + .query(query) + .collectionNameExpression("headers.collection") + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + private MongoDbOutboundGatewaySpec queryOutboundGatewayWithTemplate(String query, boolean expectSingleResult) { + return MongoDb.outboundGateway(mongoTemplate()) + .query(query) + .collectionName(COLLECTION_NAME) + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + private MongoDbOutboundGatewaySpec queryExpressionOutboundGateway(boolean expectSingleResult) { + return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) + .queryExpression("headers.query") + .collectionName(COLLECTION_NAME) + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + private MongoDbOutboundGatewaySpec queryExpressionOutboundGateway(boolean expectSingleResult, int maxResults) { + return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) + .queryExpression("new BasicQuery('{''address.state'' : ''PA''}').limit(" + maxResults + ")") + .collectionName(COLLECTION_NAME) + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + private MongoDbOutboundGatewaySpec queryFunctionOutboundGateway(boolean expectSingleResult) { + return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) + .queryFunction(msg -> + Query.query(Criteria.where("name") + .is(msg.getPayload()))) + .collectionNameExpression("headers.collection") + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + private MongoDbOutboundGatewaySpec collectionNameFunctionOutboundGateway(boolean expectSingleResult) { + return MongoDb.outboundGateway(mongoDbFactory(), mongoConverter()) + .queryExpression("headers.query") + .collectionNameFunction(Message::getPayload) + .expectSingleResult(expectSingleResult) + .entityClass(Person.class); + } + + } + +} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml new file mode 100644 index 0000000000..e7af7ee726 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests-context.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java new file mode 100644 index 0000000000..b9d4ddd34f --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayTests.java @@ -0,0 +1,318 @@ +/* + * 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.integration.mongodb.outbound; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; + +import org.bson.Document; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.BulkOperations; +import org.springframework.data.mongodb.core.MongoOperations; +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.common.LiteralExpression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.integration.expression.FunctionExpression; +import org.springframework.integration.mongodb.rules.MongoDbAvailable; +import org.springframework.integration.mongodb.rules.MongoDbAvailableTests; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.integration.test.util.TestUtils; +import org.springframework.messaging.Message; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Xavier Padró + * @since 5.0 + */ +@ContextConfiguration +@RunWith(SpringJUnit4ClassRunner.class) +@DirtiesContext +public class MongoDbOutboundGatewayTests extends MongoDbAvailableTests { + + private static final String COLLECTION_NAME = "data"; + + private static final SpelExpressionParser PARSER = new SpelExpressionParser(); + + @Autowired + private BeanFactory beanFactory; + + @Autowired + private MongoOperations mongoTemplate; + + @Autowired + private MongoConverter mongoConverter; + + @Autowired + private MongoDbFactory mongoDbFactory; + + @Before + public void setUp() { + BulkOperations bulkOperations = this.mongoTemplate.bulkOps(BulkOperations.BulkMode.ORDERED, COLLECTION_NAME); + bulkOperations.insert(Arrays.asList( + this.createPerson("Artem"), + this.createPerson("Gary"), + this.createPerson("Oleg"), + this.createPerson("Xavi"))); + bulkOperations.execute(); + } + + @After + public void cleanUp() { + mongoTemplate.dropCollection(COLLECTION_NAME); + } + + @SuppressWarnings("ConstantConditions") + @Test + @MongoDbAvailable + public void testNoFactorySpecified() { + MongoDbFactory nullFactory = null; + + try { + new MongoDbOutboundGateway(nullFactory); + Assert.fail("Expected the test case to throw an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertEquals("MongoDbFactory translator must not be null!", e.getMessage()); + } + } + + @SuppressWarnings("ConstantConditions") + @Test + @MongoDbAvailable + public void testNoTemplateSpecified() { + MongoOperations mongoTemplate = null; + + try { + new MongoDbOutboundGateway(mongoTemplate); + Assert.fail("Expected the test case to throw an IllegalArgumentException"); + } + catch (IllegalArgumentException e) { + assertEquals("mongoTemplate must not be null.", e.getMessage()); + } + } + + @Test + @MongoDbAvailable + public void testNoQuerySpecified() { + Message message = MessageBuilder.withPayload("test").build(); + MongoDbOutboundGateway gateway = createGateway(); + + try { + gateway.afterPropertiesSet(); + gateway.handleRequestMessage(message); + Assert.fail("Expected the test case to throw an IllegalArgumentException"); + } + catch (IllegalStateException e) { + assertEquals("no query specified", e.getMessage()); + } + } + + @Test + @MongoDbAvailable + public void testListOfResultsWithQueryExpressionAndLimit() { + Message message = MessageBuilder.withPayload("").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setQueryExpression( + PARSER.parseExpression("new BasicQuery('{''address.state'' : ''PA''}').limit(2)")); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + List persons = getPersonsFromResult(result); + assertEquals(2, persons.size()); + } + + @Test + @MongoDbAvailable + public void testListOfResultsWithQueryFunction() { + Message message = MessageBuilder.withPayload("Xavi").build(); + MongoDbOutboundGateway gateway = createGateway(); + Function, Query> queryFunction = + msg -> new BasicQuery("{'name' : '" + msg.getPayload() + "'}"); + FunctionExpression> functionExpression = new FunctionExpression<>(queryFunction); + gateway.setQueryExpression(functionExpression); + gateway.setExpectSingleResult(true); + gateway.setEntityClass(Person.class); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + Person person = (Person) result; + + assertEquals("Xavi", person.getName()); + } + + @Test + @MongoDbAvailable + public void testListOfResultsWithQueryExpressionNotInitialized() { + MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(mongoDbFactory); + gateway.setBeanFactory(beanFactory); + gateway.setMongoConverter(mongoConverter); + try { + gateway.afterPropertiesSet(); + Assert.fail("Expected the test case to throw an IllegalStateException"); + } + catch (IllegalStateException e) { + assertEquals("no query specified", e.getMessage()); + } + } + + @Test + @MongoDbAvailable + public void testListOfResultsWithQueryExpression() throws Exception { + Message message = MessageBuilder.withPayload("{}").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setEntityClass(Person.class); + gateway.setQueryExpression(PARSER.parseExpression("payload")); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + List persons = getPersonsFromResult(result); + assertEquals(4, persons.size()); + } + + @Test + @MongoDbAvailable + public void testListOfResultsWithQueryExpressionReturningOneResult() throws Exception { + Message message = MessageBuilder.withPayload("{name : 'Xavi'}").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setEntityClass(Person.class); + gateway.setQueryExpression(PARSER.parseExpression("payload")); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + List persons = getPersonsFromResult(result); + assertEquals(1, persons.size()); + assertEquals("Xavi", persons.get(0).getName()); + } + + @Test + @MongoDbAvailable + public void testSingleResultWithQueryExpressionAsString() throws Exception { + Message message = MessageBuilder.withPayload("{name : 'Artem'}").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setQueryExpression(PARSER.parseExpression("payload")); + gateway.setExpectSingleResult(true); + gateway.setEntityClass(Person.class); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + Person person = (Person) result; + assertEquals("Artem", person.getName()); + } + + @Test + @MongoDbAvailable + public void testSingleResultWithQueryExpressionAsQuery() throws Exception { + Message message = MessageBuilder.withPayload("").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setQueryExpression(PARSER.parseExpression("new BasicQuery('{''name'' : ''Gary''}')")); + gateway.setExpectSingleResult(true); + gateway.setEntityClass(Person.class); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + Person person = (Person) result; + assertEquals("Gary", person.getName()); + } + + @Test + @MongoDbAvailable + public void testSingleResultWithQueryExpressionAndNoEntityClass() { + Message message = MessageBuilder.withPayload("").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); + gateway.setExpectSingleResult(true); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + Document person = (Document) result; + assertEquals("Xavi", person.get("name")); + } + + @Test + @MongoDbAvailable + public void testWithNullCollectionNameExpression() throws Exception { + MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(mongoDbFactory); + gateway.setBeanFactory(beanFactory); + gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); + gateway.setExpectSingleResult(true); + + try { + gateway.afterPropertiesSet(); + Assert.fail("Expected the test case to throw an IllegalArgumentException"); + } + catch (IllegalStateException e) { + assertEquals("no collection name specified", e.getMessage()); + } + } + + @Test + @MongoDbAvailable + public void testWithCollectionNameExpressionSpecified() throws Exception { + Message message = MessageBuilder.withPayload("").build(); + MongoDbOutboundGateway gateway = createGateway(); + gateway.setQueryExpression(new LiteralExpression("{name : 'Xavi'}")); + gateway.setExpectSingleResult(true); + gateway.setCollectionNameExpression(new LiteralExpression("anotherCollection")); + gateway.afterPropertiesSet(); + + Object result = gateway.handleRequestMessage(message); + + assertNull(result); + LiteralExpression collectionNameExpression = + (LiteralExpression) TestUtils.getPropertyValue(gateway, "collectionNameExpression"); + assertNotNull(collectionNameExpression); + assertEquals("anotherCollection", collectionNameExpression.getValue()); + } + + @SuppressWarnings("unchecked") + private List getPersonsFromResult(Object result) { + return (List) result; + } + + private MongoDbOutboundGateway createGateway() { + MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(mongoDbFactory); + gateway.setBeanFactory(beanFactory); + gateway.setCollectionNameExpression(new LiteralExpression("data")); + + return gateway; + } + +} diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml new file mode 100644 index 0000000000..1a20cd2ad3 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests-context.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java new file mode 100644 index 0000000000..8dff1443f1 --- /dev/null +++ b/spring-integration-mongodb/src/test/java/org/springframework/integration/mongodb/outbound/MongoDbOutboundGatewayXmlTests.java @@ -0,0 +1,167 @@ +/* + * 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.integration.mongodb.outbound; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.data.mongodb.MongoDbFactory; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.mongodb.rules.MongoDbAvailable; +import org.springframework.integration.mongodb.rules.MongoDbAvailableTests; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.messaging.Message; +import org.springframework.messaging.PollableChannel; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Xavier Padró + * @since 5.0 + */ +@RunWith(SpringRunner.class) +@DirtiesContext +public class MongoDbOutboundGatewayXmlTests extends MongoDbAvailableTests { + + private static final String COLLECTION_NAME = "data"; + + @Autowired + private ApplicationContext context; + + @Before + public void setUp() throws Exception { + MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); + MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory); + + mongoTemplate.save(this.createPerson("Artem"), COLLECTION_NAME); + mongoTemplate.save(this.createPerson("Gary"), COLLECTION_NAME); + mongoTemplate.save(this.createPerson("Oleg"), COLLECTION_NAME); + mongoTemplate.save(this.createPerson("Xavi"), COLLECTION_NAME); + } + + @After + public void cleanUp() throws Exception { + MongoDbFactory mongoDbFactory = this.prepareMongoFactory(); + MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory); + + mongoTemplate.dropCollection(COLLECTION_NAME); + } + + + @Test + @MongoDbAvailable + public void testSingleQuery() throws Exception { + EventDrivenConsumer consumer = context.getBean("gatewaySingleQuery", EventDrivenConsumer.class); + PollableChannel outChannel = context.getBean("out", PollableChannel.class); + + Message message = MessageBuilder.withPayload("").build(); + consumer.getHandler().handleMessage(message); + + Message result = outChannel.receive(10000); + Person person = getPerson(result); + assertEquals("Xavi", person.getName()); + } + + @Test + @MongoDbAvailable + public void testSingleQueryWithTemplate() throws Exception { + EventDrivenConsumer consumer = context.getBean("gatewayWithTemplate", EventDrivenConsumer.class); + PollableChannel outChannel = context.getBean("out", PollableChannel.class); + + Message message = MessageBuilder.withPayload("").build(); + consumer.getHandler().handleMessage(message); + + Message result = outChannel.receive(10000); + Person person = getPerson(result); + assertEquals("Xavi", person.getName()); + } + + @Test + @MongoDbAvailable + public void testSingleQueryExpression() throws Exception { + EventDrivenConsumer consumer = context.getBean("gatewaySingleQueryExpression", EventDrivenConsumer.class); + PollableChannel outChannel = context.getBean("out", PollableChannel.class); + + Message message = MessageBuilder + .withPayload("") + .setHeader("query", "{'name' : 'Gary'}") + .setHeader("collectionName", "data") + .build(); + + consumer.getHandler().handleMessage(message); + + Message result = outChannel.receive(10000); + Person person = getPerson(result); + assertEquals("Gary", person.getName()); + } + + @Test + @MongoDbAvailable + public void testQueryExpression() throws Exception { + EventDrivenConsumer consumer = context.getBean("gatewayQueryExpression", EventDrivenConsumer.class); + PollableChannel outChannel = context.getBean("out", PollableChannel.class); + + Message message = MessageBuilder + .withPayload("") + .setHeader("query", "{}") + .setHeader("collectionName", "data") + .build(); + + consumer.getHandler().handleMessage(message); + + Message result = outChannel.receive(10000); + List persons = getPersons(result); + assertEquals(4, persons.size()); + } + + @Test + @MongoDbAvailable + public void testQueryExpressionWithLimit() throws Exception { + EventDrivenConsumer consumer = context.getBean("gatewayQueryExpressionLimit", EventDrivenConsumer.class); + PollableChannel outChannel = context.getBean("out", PollableChannel.class); + + Message message = MessageBuilder + .withPayload("") + .setHeader("collectionName", "data") + .build(); + + consumer.getHandler().handleMessage(message); + + Message result = outChannel.receive(10000); + List persons = getPersons(result); + assertEquals(2, persons.size()); + } + + private Person getPerson(Message message) { + return (Person) message.getPayload(); + } + + @SuppressWarnings("unchecked") + private List getPersons(Message message) { + return (List) message.getPayload(); + } + +} diff --git a/src/reference/asciidoc/mongodb.adoc b/src/reference/asciidoc/mongodb.adoc index 2cd3131305..682750df35 100644 --- a/src/reference/asciidoc/mongodb.adoc +++ b/src/reference/asciidoc/mongodb.adoc @@ -22,23 +22,23 @@ To connect to MongoDB you can use an implementation of the `MongoDbFactory` inte ---- public interface MongoDbFactory { - /** - * Creates a default {@link DB} instance. - * - * @return the DB instance - * @throws DataAccessException - */ - DB getDb() throws DataAccessException; + /** + * Creates a default {@link DB} instance. + * + * @return the DB instance + * @throws DataAccessException + */ + DB getDb() throws DataAccessException; - /** - * Creates a {@link DB} instance to access the database with the given name. - * - * @param dbName must not be {@literal null} or empty. - * - * @return the DB instance - * @throws DataAccessException - */ - DB getDb(String dbName) throws DataAccessException; + /** + * Creates a {@link DB} instance to access the database with the given name. + * + * @param dbName must not be {@literal null} or empty. + * + * @return the DB instance + * @throws DataAccessException + */ + DB getDb(String dbName) throws DataAccessException; } ---- @@ -290,3 +290,120 @@ and other attributes that are common across all other inbound adapters (e.g., 'c The example above is relatively simple and static since it has a literal value for the `collection-name`. Sometimes you may need to change this value at runtime based on some condition. To do that, simply use `collection-name-expression` where the provided expression can be any valid SpEL expression. + +[[mongodb-outbound-gateway]] +=== MongoDB Outbound Gateway + +Starting with _version 5.0_, the MongoDb Outbound Gateway is provided and it allows you to query a database by sending a Message to its request channel. +The gateway will then send the response to the reply channel. +The Message payload and headers can be used to specify the query, as well as collection name. + +[source,xml] +---- + +---- + +* `collection-name` or `collection-name-expression` - identifies the name of the MongoDb collection to use; +* `mongo-converter` - reference to an instance of `o.s.data.mongodb.core.convert.MongoConverter` to assist with converting a raw java object to a JSON document representation +* `mongodb-factory` - reference to an instance of `o.s.data.mongodb.MongoDbFactory` +* `mongo-template` - reference to an instance of `o.s.data.mongodb.core.MongoTemplate` (NOTE: you can not have both mongo-template and mongodb-factory set) +* `entity-class` - the fully qualified name of the entity class to be passed to `find(..)` or `findOne(..)` method in MongoTemplate. +If this attribute is not provided the default value is `org.bson.Document`; +* `query` or `query-expression` - specifies the MongoDb query. +Please refer to http://www.mongodb.org/display/DOCS/Querying[MongoDB documentation] for more query samples. + +==== Configuring with Java Configuration + +The following Spring Boot application provides an example of configuring the outbound gateway using Java configuration: + +[source, java] +---- +@SpringBootApplication +public class MongoDbJavaApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(MongoDbJavaApplication.class) + .web(false) + .run(args); + } + + @Autowired + private MongoDbFactory mongoDbFactory; + + @Bean + public MessageChannel requestChannel() { + return new DirectChannel(); + } + + @Bean + public MessageChannel replyChannel() { + return new QueueChannel(5); + } + + @Bean + @ServiceActivator(inputChannel = "requestChannel") + public MessageHandler mongoDbOutboundGateway() { + MongoDbOutboundGateway gateway = new MongoDbOutboundGateway(this.mongoDbFactory); + gateway.setCollectionNameExpressionString("'foo'"); + gateway.setQueryExpressionString("'{''name'' : ''Bob''}'"); + gateway.setExpectSingleResult(true); + gateway.setEntityClass(Person.class); + gateway.setOutputChannelName("replyChannel"); + + return gateway; + } + + @Bean + @ServiceActivator(inputChannel = "replyChannel") + public MessageHandler handler() { + return message -> System.out.println(message.getPayload()); + } +} +---- + +==== Configuring with the Java DSL + +The following Spring Boot application provides an example of configuring the Outbound Gateway using the Java DSL: + +[source, java] +---- +@SpringBootApplication +public class MongoDbJavaApplication { + + public static void main(String[] args) { + new SpringApplicationBuilder(MongoDbJavaApplication.class) + .web(false) + .run(args); + } + + @Autowired + private MongoDbFactory; + + @Autowired + private MongoConverter; + + + @Bean + public IntegrationFlow gatewaySingleQueryFlow() { + return f -> f + .handle(queryOutboundGateway()) + .channel(c -> c.queue("retrieveResults")); + } + + private MongoDbOutboundGatewaySpec queryOutboundGateway() { + return MongoDb.outboundGateway(this.mongoDbFactory, this.mongoConverter) + .query("{name : 'Bob'}") + .collectionNameFunction(m -> m.getHeaders().get("collection")) + .expectSingleResult(true) + .entityClass(Person.class); + } + +} +---- \ No newline at end of file diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index ab948b2fb0..b96edf25b1 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -9,6 +9,10 @@ development process. [[x5.0-new-components]] === New Components +==== MongoDB Outbound Gateway + +The new `MongoDbOutboundGateway` allows you to make queries to the database on demand by sending a message to its request channel. +See <> for more information. [[x5.0-general]] === General Changes