diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java index 03140cf2b4..d3296e494f 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -15,6 +15,8 @@ */ package org.springframework.integration.jpa.config.xml; +import org.w3c.dom.Element; + import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; @@ -23,20 +25,17 @@ import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.config.xml.AbstractPollingInboundChannelAdapterParser; import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.jpa.inbound.JpaPollingChannelAdapter; -import org.w3c.dom.Element; /** * The JPA Inbound Channel adapter parser * * @author Amol Nayak * @author Gunnar Hillert - * * @since 2.2 - * */ -public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser{ - +public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser { + @Override protected BeanMetadataElement parseSource(Element element, ParserContext parserContext) { final BeanDefinitionBuilder jpaPollingChannelAdapterBuilder = BeanDefinitionBuilder @@ -44,7 +43,12 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext); - IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "max-number-of-results"); + BeanDefinition definition = IntegrationNamespaceUtils + .createExpressionDefinitionFromValueOrExpression("max-number-of-results", "max-results-expression", + parserContext, element, false); + if (definition != null) { + jpaExecutorBuilder.addPropertyValue("maxResultsExpression", definition); + } IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-after-poll"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-in-batch"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "expect-single-result"); @@ -59,4 +63,5 @@ public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChanne return jpaPollingChannelAdapterBuilder.getBeanDefinition(); } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java index 420f803987..1cf5b664fe 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/RetrievingJpaOutboundGatewayParser.java @@ -15,8 +15,6 @@ */ package org.springframework.integration.jpa.config.xml; -import static org.springframework.integration.config.xml.IntegrationNamespaceUtils.createExpressionDefinitionFromValueOrExpression; - import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; @@ -31,9 +29,7 @@ import org.springframework.integration.jpa.support.OutboundGatewayType; * * @author Amol Nayak * @author Gunnar Hillert - * * @since 2.2 - * */ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatewayParser { @@ -44,14 +40,20 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getOutboundGatewayJpaExecutorBuilder(gatewayElement, parserContext); - - BeanDefinition firstResultExpression = createExpressionDefinitionFromValueOrExpression("first-result", - "first-result-expression", parserContext, gatewayElement, false); - - if(firstResultExpression != null) { + BeanDefinition firstResultExpression = IntegrationNamespaceUtils + .createExpressionDefinitionFromValueOrExpression("first-result", "first-result-expression", + parserContext, gatewayElement, false); + if (firstResultExpression != null) { jpaExecutorBuilder.addPropertyValue("firstResultExpression", firstResultExpression); } - IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "max-number-of-results"); + + BeanDefinition maxResultsExpression = IntegrationNamespaceUtils + .createExpressionDefinitionFromValueOrExpression("max-number-of-results", "max-results-expression", + parserContext, gatewayElement, false); + if (maxResultsExpression != null) { + jpaExecutorBuilder.addPropertyValue("maxResultsExpression", maxResultsExpression); + } + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-in-batch"); IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "expect-single-result"); @@ -64,6 +66,8 @@ public class RetrievingJpaOutboundGatewayParser extends AbstractJpaOutboundGatew jpaOutboundGatewayBuilder.addConstructorArgReference(jpaExecutorBeanName); jpaOutboundGatewayBuilder.addPropertyValue("gatewayType", OutboundGatewayType.RETRIEVING); + return jpaOutboundGatewayBuilder; } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java index 29279c2a63..204e348046 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.Message; import org.springframework.integration.MessagingException; import org.springframework.integration.expression.IntegrationEvaluationContextAware; @@ -65,7 +66,8 @@ import org.springframework.util.Assert; */ public class JpaExecutor implements InitializingBean, BeanFactoryAware, IntegrationEvaluationContextAware { - private volatile JpaOperations jpaOperations; + private final JpaOperations jpaOperations; + private volatile List jpaParameters; private volatile Class entityClass; @@ -73,8 +75,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat private volatile String nativeQuery; private volatile String namedQuery; - /** 0 means all possible objects shall be retrieved. */ - private volatile int maxNumberOfResults = 0; + private volatile Expression maxResultsExpression; private volatile Expression firstResultExpression; @@ -91,7 +92,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat /** * Indicates that whether only the payload of the passed in {@link Message} * will be used as a source of parameters. The is 'true' by default because as a - * default a {@link BeanPropertyJpaParameterSourceFactory} implementation is + * default a {@link BeanPropertyParameterSourceFactory} implementation is * used for the sqlParameterSourceFactory property. */ private volatile Boolean usePayloadAsParameterSource = null; @@ -163,7 +164,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat if (this.parameterSourceFactory == null) { ExpressionEvaluatingParameterSourceFactory expressionSourceFactory = new ExpressionEvaluatingParameterSourceFactory(this.beanFactory); - expressionSourceFactory.setParameters(jpaParameters); + expressionSourceFactory.setParameters(this.jpaParameters); this.parameterSourceFactory = expressionSourceFactory; } @@ -192,9 +193,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat if (this.usePayloadAsParameterSource == null) { this.usePayloadAsParameterSource = true; } - } - } /** @@ -206,7 +205,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat * not necessarily correlate with the number of rows effected in the database. * * @param message - * @return Either the number of affected entities when using a JPAQL query. When using a merge/persist the updated/inserted itself is returned. + * @return Either the number of affected entities when using a JPQL query. + * When using a merge/persist the updated/inserted itself is returned. */ public Object executeOutboundJpaOperation(final Message message) { @@ -214,22 +214,16 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat ParameterSource parameterSource = null; if (this.jpaQuery != null || this.nativeQuery != null || this.namedQuery != null) { - parameterSource = determineParameterSource(message); + parameterSource = this.determineParameterSource(message); } if (this.jpaQuery != null) { - result = this.jpaOperations.executeUpdate(this.jpaQuery, parameterSource); - } else if (this.nativeQuery != null) { - result = this.jpaOperations.executeUpdateWithNativeQuery(this.nativeQuery, parameterSource); - } else if (this.namedQuery != null) { - result = this.jpaOperations.executeUpdateWithNamedQuery(this.namedQuery, parameterSource); - } else { @@ -238,8 +232,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat result = message.getPayload(); } else if (PersistMode.MERGE.equals(this.persistMode)) { - final Object mergedEntity = this.jpaOperations.merge(message.getPayload()); - result = mergedEntity; + result = this.jpaOperations.merge(message.getPayload()); } else if (PersistMode.DELETE.equals(this.persistMode)) { this.jpaOperations.delete(message.getPayload()); @@ -270,16 +263,17 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat public Object poll(final Message requestMessage) { final Object payload; final List result; + int maxNumberOfResults = this.evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression); if (requestMessage == null) { - result = doPoll(this.parameterSource, 0); + result = this.doPoll(this.parameterSource, 0, maxNumberOfResults); } else { int firstResult = 0; if(firstResultExpression != null) { - firstResult = getFirstResult(requestMessage); + firstResult = this.getFirstResult(requestMessage); } - ParameterSource parameterSource = determineParameterSource(requestMessage); - result = doPoll(parameterSource, firstResult); + ParameterSource parameterSource = this.determineParameterSource(requestMessage); + result = this.doPoll(parameterSource, firstResult, maxNumberOfResults); } if (result.isEmpty()) { @@ -293,8 +287,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat } else { throw new MessagingException(requestMessage, - "The Jpa operation returned more than " - + "1 result object but expectSingleResult was 'true'."); + "The Jpa operation returned more than 1 result object but expectSingleResult was 'true'."); } } else { @@ -322,28 +315,34 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat } private int getFirstResult(final Message requestMessage) { - int firstResult = 0; - Object firstRecordEvaluationResult = firstResultExpression.getValue(evaluationContext, requestMessage); - if(firstRecordEvaluationResult != null) { - if(firstRecordEvaluationResult instanceof Number) { - firstResult = ((Number)firstRecordEvaluationResult).intValue(); - } - else if(firstRecordEvaluationResult instanceof String){ - try { - firstResult = Integer.parseInt((String)firstRecordEvaluationResult); + return this.evaluateExpressionForNumericResult(requestMessage, this.firstResultExpression); + } + + private int evaluateExpressionForNumericResult(final Message requestMessage, Expression expression) { + int evaluatedResult = 0; + if(expression != null) { + Object evaluationResult = expression.getValue(this.evaluationContext, requestMessage); + if(evaluationResult != null) { + if(evaluationResult instanceof Number) { + evaluatedResult = ((Number) evaluationResult).intValue(); } - catch (NumberFormatException e) { - throw new IllegalArgumentException( - "Value " + firstRecordEvaluationResult + " passed as firstRecord cannot be " + - "parsed to a number"); + else if(evaluationResult instanceof String){ + try { + evaluatedResult = Integer.parseInt((String) evaluationResult); + } + catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Value " + evaluationResult + " passed as cannot be " + + "parsed to a number, expected to be numeric"); + } + } + else { + throw new IllegalArgumentException("Expected the value to be a Number" + + " got " + evaluationResult.getClass().getName()); } - } - else { - throw new IllegalArgumentException("Expected the value of the firstRecord to be a Number" + - " got " + firstRecordEvaluationResult.getClass().getName()); } } - return firstResult; + return evaluatedResult; } private ParameterSource determineParameterSource(final Message requestMessage) { @@ -361,26 +360,25 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat * Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}. */ public Object poll() { - return poll(null); + return this.poll(null); } - protected List doPoll(ParameterSource jpaQLParameterSource, int firstResult) { + protected List doPoll(ParameterSource jpaQLParameterSource, int firstResult, int maxNumberOfResults) { List payload = null; if (this.jpaQuery != null) { - payload = jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource, + payload = this.jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource, firstResult, maxNumberOfResults); } else if (this.nativeQuery != null) { - payload = jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource, + payload = this.jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource, firstResult, maxNumberOfResults); } else if (this.namedQuery != null) { - payload = jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource, + payload = this.jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource, firstResult, maxNumberOfResults); } else if (this.entityClass != null) { - payload = jpaOperations.getResultListForClass(this.entityClass, - firstResult, maxNumberOfResults); + payload = this.jpaOperations.getResultListForClass(this.entityClass, firstResult, maxNumberOfResults); } else { throw new IllegalStateException("For the polling operation, one of " @@ -422,7 +420,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat public void setNativeQuery(String nativeQuery) { Assert.isTrue(this.namedQuery == null && this.jpaQuery == null, "You can define only one of the " - + "properties 'jpaQuery', 'nativeQuery', 'namedQuery'");; + + "properties 'jpaQuery', 'nativeQuery', 'namedQuery'"); Assert.hasText(nativeQuery, "nativeQuery must neither be null nor empty."); this.nativeQuery = nativeQuery; @@ -490,8 +488,7 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat * * @param parameterSourceFactory Must not be null */ - public void setParameterSourceFactory( - ParameterSourceFactory parameterSourceFactory) { + public void setParameterSourceFactory(ParameterSourceFactory parameterSourceFactory) { Assert.notNull(parameterSourceFactory, "parameterSourceFactory must not be null."); this.parameterSourceFactory = parameterSourceFactory; } @@ -526,19 +523,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat this.expectSingleResult = expectSingleResult; } - /** - * Set the max number of results to retrieve from the database. Defaults to - * 0, which means that all possible objects shall be retrieved. - * - * @param maxNumberOfResults Must not be negative. - * - * @see Query#setMaxResults(int) - */ - public void setMaxNumberOfResults(int maxNumberOfResults) { - Assert.isTrue(maxNumberOfResults >= 0, "maxNumberOfResults must not be negative."); - this.maxNumberOfResults = maxNumberOfResults; - } - /** * Sets the expression that will be evaluated to get the first result in the query executed. * If a null expression is set, all the results in the result set will be retrieved @@ -552,6 +536,30 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat } + + /** + * Sets the expression for maximum number of results expression. It has be a non null value + * Not setting one will default to the behavior of fetching all the records + * + * @param maxResultsExpression + */ + public void setMaxResultsExpression(Expression maxResultsExpression) { + Assert.notNull(maxResultsExpression, "maxResultsExpression cannot be null"); + this.maxResultsExpression = maxResultsExpression; + } + + /** + * Set the max number of results to retrieve from the database. Defaults to + * 0, which means that all possible objects shall be retrieved. + * + * @param maxNumberOfResults Must not be negative. + * + * @see Query#setMaxResults(int) + */ + public void setMaxNumberOfResults(int maxNumberOfResults) { + this.setMaxResultsExpression(new LiteralExpression("" + maxNumberOfResults)); + } + /** * Sets the evaluation context for evaluating the expression to get the from record of the * result set retrieved by the retrieving gateway. @@ -559,8 +567,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware, Integrat * @param evaluationContext */ @Override - public void setIntegrationEvaluationContext( - EvaluationContext evaluationContext) { + public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) { this.evaluationContext = evaluationContext; } + } diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java index e7d9628e63..b6aaec97ac 100644 --- a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java @@ -132,7 +132,7 @@ public interface JpaOperations { * * @param query Must not be null or empty * @param firstResult The first result - * @param maxNumberOfResults Must be a non-negative value + * @param maxNumberOfResults Must be a non-negative value, any negative or zero will be ignored * @param source the Parameter source for this query to be executed, if none then set null * @return List of found entities */ diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd index 18cdaccec4..f6c8b30c03 100644 --- a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd +++ b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-3.0.xsd @@ -419,10 +419,21 @@ Specifies the maximum number of entities that shall be returned by a JPA Operation. Using this attribute you basically set - the 'maxResults' property of the JPA Query object. + the 'maxResults' property of the JPA Query object. This attribute is mutually + exclusive to max-results-expression attribute. + + + + Specifies the expression for the maximum number of entities that shall be returned + by a JPA Operation. Using this attribute you basically set + the 'maxResults' property of the JPA Query object. This attribute is mutually + exclusive to max-number-of-results attribute. + + + entityClass = TestUtils.getPropertyValue(jpaExecutor, "entityClass", Class.class); + + assertEquals("org.springframework.integration.jpa.test.entity.StudentDomain", entityClass.getName()); + + final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class); + + assertNotNull(jpaOperations); + + SpelExpression expression = TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", SpelExpression.class); + + assertNotNull(expression); + + assertEquals("@maxNumberOfResults", TestUtils.getPropertyValue(expression, "expression")); + + } + + @Test public void testJpaExecutorBeanIdNaming() throws Exception { diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml index fbbe981f31..dff8993e48 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml @@ -28,5 +28,16 @@ channel="out"> - + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java index 0173050670..776ec14df2 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java @@ -76,8 +76,10 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class); assertNotNull(jpaOperations); assertTrue(TestUtils.getPropertyValue(jpaExecutor, "expectSingleResult", Boolean.class)); - final Integer maxNumberOfResults = TestUtils.getPropertyValue(jpaExecutor, "maxNumberOfResults", Integer.class); - assertEquals(Integer.valueOf(55), maxNumberOfResults); + final LiteralExpression maxResultsExpression = + TestUtils.getPropertyValue(jpaExecutor, "maxResultsExpression", LiteralExpression.class); + assertNotNull(maxResultsExpression); + assertEquals("55", TestUtils.getPropertyValue(maxResultsExpression, "literalValue")); } @Test @@ -102,6 +104,17 @@ public class JpaOutboundGatewayParserTests extends AbstractRequestHandlerAdvice assertEquals("header['firstResult']", TestUtils.getPropertyValue(firstResultExpression, "expression", String.class)); } + @Test + public void testRetrievingJpaOutboundGatewayParserWithMaxResultExpression() throws Exception { + setUp("JpaOutboundGatewayParserTests.xml", getClass(), "retrievingJpaOutboundGatewayWithMaxResultExpression"); + final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class); + Expression maxNumberOfResultExpression = + TestUtils.getPropertyValue(jpaOutboundGateway, "jpaExecutor.maxResultsExpression", Expression.class); + assertNotNull(maxNumberOfResultExpression); + assertEquals(SpelExpression.class, maxNumberOfResultExpression.getClass()); + assertEquals("header['maxResults']", TestUtils.getPropertyValue(maxNumberOfResultExpression, "expression", String.class)); + } + @Test public void testUpdatingJpaOutboundGatewayParser() throws Exception { setUp("JpaOutboundGatewayParserTests.xml", getClass(), "updatingJpaOutboundGateway"); diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml index 1df8275b01..33b20783ae 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml @@ -18,19 +18,18 @@ entity-manager-factory="entityManagerFactory" auto-startup="true" entity-class="org.springframework.integration.jpa.test.entity.StudentDomain" - expect-single-result="true" order="1" max-number-of-results="55" request-channel="in" reply-channel="out" reply-timeout="100" + expect-single-result="true" requires-reply="false"/> + + - - - results = jpaOperations.getResultListForQuery(query, null, 2, 0); assertEquals(1, results.size()); } + + public void testWithNegativeMaxNumberofResults() { + JpaOperations jpaOperations = getJpaOperations(entityManager); + String query = "select s from Student s"; + List results = jpaOperations.getResultListForQuery(query, null, 0, -1); + assertEquals(3, results.size()); + } } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/JpaExecutorTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/JpaExecutorTests.java index 6c4dad7034..0012eaddf6 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/JpaExecutorTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/JpaExecutorTests.java @@ -27,6 +27,7 @@ import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.expression.common.LiteralExpression; +import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.integration.Message; import org.springframework.integration.jpa.support.JpaParameter; import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory; @@ -52,6 +53,8 @@ public class JpaExecutorTests { @Autowired protected EntityManager entityManager; + private final StandardEvaluationContext ctx = new StandardEvaluationContext(); + /** * In this test, the {@link JpaExecutor}'s poll method will be called without * specifying a 'query', 'namedQuery' or 'entityClass' property. This should @@ -63,7 +66,7 @@ public class JpaExecutorTests { public void testExecutePollWithNoEntityClassSpecified() throws Exception { final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class)); - + jpaExecutor.afterPropertiesSet(); try { jpaExecutor.poll(); } catch (IllegalStateException e) { @@ -210,27 +213,12 @@ public class JpaExecutorTests { return executor; } - @Test - public void testNegativeMaxNumberOfResults() throws Exception { - - final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class)); - - try { - jpaExecutor.setMaxNumberOfResults(-10); - } catch (IllegalArgumentException e) { - Assert.assertEquals("maxNumberOfResults must not be negative.", e.getMessage()); - return; - } - - Assert.fail("Was expecting an IllegalStateException to be thrown."); - - } - @Test public void testResultStartingFromThirdRecordForJPAQuery() throws Exception { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setJpaQuery("select s from Student s"); jpaExecutor.setFirstResultExpression(new LiteralExpression("2")); + jpaExecutor.setIntegrationEvaluationContext(ctx); jpaExecutor.afterPropertiesSet(); List results = (List)jpaExecutor.poll(MessageBuilder.withPayload("").build()); @@ -243,6 +231,7 @@ public class JpaExecutorTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setNativeQuery("select * from Student s"); jpaExecutor.setFirstResultExpression(new LiteralExpression("2")); + jpaExecutor.setIntegrationEvaluationContext(ctx); jpaExecutor.afterPropertiesSet(); List results = (List)jpaExecutor.poll(MessageBuilder.withPayload("").build()); Assert.assertNotNull(results); @@ -254,6 +243,7 @@ public class JpaExecutorTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setNamedQuery("selectAllStudents"); jpaExecutor.setFirstResultExpression(new LiteralExpression("2")); + jpaExecutor.setIntegrationEvaluationContext(ctx); jpaExecutor.afterPropertiesSet(); List results = (List)jpaExecutor.poll(MessageBuilder.withPayload("").build()); Assert.assertNotNull(results); @@ -265,9 +255,22 @@ public class JpaExecutorTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setEntityClass(StudentDomain.class); jpaExecutor.setFirstResultExpression(new LiteralExpression("2")); + jpaExecutor.setIntegrationEvaluationContext(ctx); jpaExecutor.afterPropertiesSet(); List results = (List)jpaExecutor.poll(MessageBuilder.withPayload("").build()); Assert.assertNotNull(results); Assert.assertEquals(1, results.size()); } + + @Test + public void withNullMaxResultsExpression() { + final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class)); + try { + jpaExecutor.setMaxResultsExpression(null); + } catch (Exception e) { + Assert.assertEquals("maxResultsExpression cannot be null", e.getMessage()); + return; + } + Assert.fail("Expected the test case to throw an exception"); + } } \ No newline at end of file diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java index 3ed5c0de8a..b427336156 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java @@ -29,9 +29,11 @@ import javax.persistence.EntityManager; 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.support.GenericApplicationContext; +import org.springframework.expression.common.LiteralExpression; import org.springframework.integration.Message; import org.springframework.integration.MessageChannel; import org.springframework.integration.endpoint.SourcePollingChannelAdapter; @@ -94,6 +96,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setEntityClass(StudentDomain.class); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -137,6 +140,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setJpaQuery("from Student"); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -180,7 +184,8 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setJpaQuery("from Student"); - jpaExecutor.setMaxNumberOfResults(1); + jpaExecutor.setMaxResultsExpression(new LiteralExpression("1")); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -224,7 +229,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setJpaQuery("from Student s where s.firstName = 'First Two'"); - + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( @@ -273,6 +278,7 @@ public class JpaPollingChannelAdapterTests { jpaExecutor.setJpaQuery("from Student s"); jpaExecutor.setDeleteAfterPoll(true); jpaExecutor.setDeleteInBatch(true); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -332,6 +338,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setJpaQuery("from Student s where s.lastName = 'Something Else'"); jpaExecutor.setDeleteAfterPoll(true); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -371,6 +378,7 @@ public class JpaPollingChannelAdapterTests { jpaExecutor.setJpaQuery("from Student s"); jpaExecutor.setDeleteAfterPoll(true); jpaExecutor.setDeleteInBatch(false); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -417,6 +425,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setNativeQuery("select * from Student where lastName = 'Last One'"); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); @@ -460,6 +469,7 @@ public class JpaPollingChannelAdapterTests { final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); jpaExecutor.setNamedQuery("selectStudent"); + jpaExecutor.afterPropertiesSet(); final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml index d4d4138fcf..7c650e0bae 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests-context.xml @@ -21,5 +21,6 @@ request-channel="in" reply-channel="out" first-result-expression="payload" + max-results-expression="headers['maxResults']" reply-timeout="100"/> diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java index 9610b34f88..d1e9f6e891 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayIntegrationTests.java @@ -54,11 +54,11 @@ public class JpaOutboundGatewayIntegrationTests { /** * Sends a message with the payload as a integer representing the start number in the result - * set. + * set and a header with value maxResults to get the max number of results * @throws Exception */ @Test - public void retrieveFromSecondRecord() throws Exception { + public void retrieveFromSecondRecordAndMaximumOneRecord() throws Exception { responseChannel.subscribe(new MessageHandler() { @SuppressWarnings("rawtypes") @Override @@ -66,7 +66,10 @@ public class JpaOutboundGatewayIntegrationTests { assertEquals(1, ((List)message.getPayload()).size()); } }); - Message message = MessageBuilder.withPayload(2).build(); + Message message = MessageBuilder + .withPayload(1) + .setHeader("maxResults", "1") + .build(); requestChannel.send(message); } } diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml index 1fe177b46e..abf2ac8641 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml @@ -31,6 +31,7 @@ + @@ -45,6 +46,7 @@ + @@ -220,6 +222,29 @@ + + + + + + + + + + + + + + + + + + + + students = studentService.getStudents(1); + Assert.assertNotNull(students); + Assert.assertEquals(1, students.size()); + } + + @Test public void deleteNonExistingStudent() { diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java index 021d4ef829..d5e3843ab0 100644 --- a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java @@ -40,4 +40,6 @@ public interface StudentService { StudentDomain getStudent2(Long id); StudentDomain persistStudent2(StudentDomain studentToPersist); + + List getStudents(int maxNumberOfRecords); } diff --git a/src/reference/docbook/jpa.xml b/src/reference/docbook/jpa.xml index 51eed2a158..4c849f5fc8 100644 --- a/src/reference/docbook/jpa.xml +++ b/src/reference/docbook/jpa.xml @@ -408,6 +408,8 @@ auto-startup="true" ]]> ]]> @@ -449,6 +451,21 @@ MessagingException is thrown. The value defaults to false. + + + This non zero, non negative integer value tells the adapter not to select more than given number + of rows on execution of the select operation. By default, if this attribute is not set, + all the possible records are selected by given query. This attribute is mutually exclusive to + max-results-expression. Optional. + + + + + An expression mutually exclusive to max-number-of-results that can + be used to provide an expression that will be evaluated to find the maximum number of results + in a result set. Optional. + + Set this value to true if you want @@ -1138,6 +1155,7 @@ public class Student { jpa-operations="" jpa-query="" max-number-of-results="" ]]> This non zero, non negative integer value tells the adapter not to select more than given number of rows on execution of the select operation. By default, if this attribute is not set, - all the possible records are selected by given query.Optional. + all the possible records are selected by given query. This attribute is mutually exclusive to + max-results-expression. Optional. + + + + + An expression mutually exclusive to max-number-of-results that can + be used to provide an expression that will be evaluated to find the maximum number of results + in a result set. Optional. @@ -1319,4 +1345,4 @@ public class Student { - \ No newline at end of file + diff --git a/src/reference/docbook/whats-new.xml b/src/reference/docbook/whats-new.xml index 32db3c7eed..074d64a9ad 100644 --- a/src/reference/docbook/whats-new.xml +++ b/src/reference/docbook/whats-new.xml @@ -471,7 +471,17 @@ Retrieving gateways had no mechanism to specify the first record to be retrieved which is a common use case. The retrieving gateways now support specifying this parameter using a first-result and first-result-expression attributes - to the gateway definition. + to the gateway definition. . + + +
+ JPA Adapters: max-results-expression attribute + + Retrieving gateways and inbound adapters now has a flexibility to specify the maximum + number of results in a result set as an expression. We now have max-number-of-results + and max-results-expression attributes which are used to provide the + maximum number of results and the expression to compute the maximum number of results in the + result set respectively. For more information see .