diff --git a/build.gradle b/build.gradle index cc24e764c4..e8e74244f1 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,8 @@ subprojects { subproject -> // and http://www.gradle.org/docs/current/dsl/org.gradle.api.artifacts.ConfigurationContainer.html configurations { jacoco //Configuration Group used by Sonar to provide Code Coverage using JaCoCo + javaAgentSpringInstrument //Configuration Group used by the JPA Adapter during test execution + javaAgentOpenJpa //Configuration Group used by the JPA Adapter during test execution } // dependencies that are common across all java projects @@ -491,6 +493,51 @@ project('spring-integration-jmx') { } } +project('spring-integration-jpa') { + description = 'Spring Integration JPA Support' + dependencies { + compile project(":spring-integration-core") + compile "org.springframework:spring-aop:$springVersion" + compile "org.springframework:spring-orm:$springVersion" + compile "org.springframework:spring-tx:$springVersion" + compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.0.Final" + + testCompile project(":spring-integration-test") + testCompile "com.h2database:h2:1.3.166" + testCompile "hsqldb:hsqldb:1.8.0.10" + testCompile "org.apache.derby:derby:10.5.3.0_1" + testCompile "org.aspectj:aspectjrt:$aspectjVersion" + testCompile "org.aspectj:aspectjweaver:$aspectjVersion" + + testCompile "org.hibernate:hibernate-entitymanager:4.0.1.Final" + testCompile "org.eclipse.persistence:org.eclipse.persistence.jpa:2.3.2" + testCompile "org.apache.openjpa:openjpa:2.2.0" + + javaAgentSpringInstrument "org.springframework:spring-instrument:$springVersion" + javaAgentOpenJpa "org.apache.openjpa:openjpa:2.2.0" + } + + bundlor { + bundleSymbolicName = 'org.springframework.integration.jpa' + importTemplate += [ + 'org.springframework.integration.*;version="[2.1.0, 2.1.1)"', + 'org.springframework.*;version="[3.0.5, 4.0.0)"', + 'org.apache.commons.logging;version="[1.1.1, 2.0.0)"', + 'org.aopalliance.*;version="[1.0.0, 2.0.0)"', + 'javax.persistence.*;version="[1.0.0, 2.0.0)"', + 'javax.sql.*;version="0"', + 'org.w3c.dom.*;version="0"' + ] + } + + test.doFirst { + String jvmArgsSpringIntrument = "-javaagent:${configurations.javaAgentSpringInstrument.asPath}" + String jvmArgsOpenJpa = "-javaagent:${configurations.javaAgentOpenJpa.files.iterator().next()}" + jvmArgs jvmArgsSpringIntrument , jvmArgsOpenJpa + } + +} + project('spring-integration-mail') { description = 'Spring Integration Mail Support' dependencies { diff --git a/settings.gradle b/settings.gradle index e5fc2d3a78..3c5030623f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,6 +13,7 @@ include 'spring-integration-ip' include 'spring-integration-jdbc' include 'spring-integration-jms' include 'spring-integration-jmx' +include 'spring-integration-jpa' include 'spring-integration-mail' include 'spring-integration-mongodb' include 'spring-integration-redis' 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 new file mode 100644 index 0000000000..26d7ab4fec --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParser.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import org.springframework.beans.BeanMetadataElement; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +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 + * @since 2.2 + * + * + */ +public class JpaInboundChannelAdapterParser extends AbstractPollingInboundChannelAdapterParser{ + + + protected BeanMetadataElement parseSource(Element element, + ParserContext parserContext) { + + final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-after-poll"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "delete-per-row"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "expect-single-result"); + + final BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition(); + final String jpaExecutorBeanName = BeanDefinitionReaderUtils.generateBeanName(jpaExecutorBuilderBeanDefinition, parserContext.getRegistry()); + + parserContext.registerBeanComponent(new BeanComponentDefinition(jpaExecutorBuilderBeanDefinition, jpaExecutorBeanName)); + + final BeanDefinitionBuilder jpaPollingChannelAdapterBuilder = BeanDefinitionBuilder + .genericBeanDefinition(JpaPollingChannelAdapter.class); + + jpaPollingChannelAdapterBuilder.addConstructorArgReference(jpaExecutorBeanName); + + return jpaPollingChannelAdapterBuilder.getBeanDefinition(); + } +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaNamespaceHandler.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaNamespaceHandler.java new file mode 100644 index 0000000000..8d19d93393 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaNamespaceHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import org.springframework.integration.config.xml.AbstractIntegrationNamespaceHandler; + +/** + * The namespace handler for the JPA namespace + * + * @author Amol Nayak + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaNamespaceHandler extends AbstractIntegrationNamespaceHandler { + + /* (non-Javadoc) + * @see org.springframework.beans.factory.xml.NamespaceHandler#init() + */ + public void init() { + this.registerBeanDefinitionParser("inbound-channel-adapter", new JpaInboundChannelAdapterParser()); + this.registerBeanDefinitionParser("outbound-channel-adapter", new JpaOutboundChannelAdapterParser()); + this.registerBeanDefinitionParser("outbound-gateway", new JpaOutboundGatewayParser()); + } +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java new file mode 100644 index 0000000000..700298417a --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundChannelAdapterParser.java @@ -0,0 +1,84 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.AbstractOutboundChannelAdapterParser; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.jpa.outbound.JpaOutboundGatewayFactoryBean; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * The parser for JPA outbound channel adapter + * + * @author Amol Nayak + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public class JpaOutboundChannelAdapterParser extends AbstractOutboundChannelAdapterParser { + + @Override + protected boolean shouldGenerateId() { + return false; + } + + @Override + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + @Override + protected AbstractBeanDefinition parseConsumer(Element element, ParserContext parserContext) { + + final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(element, parserContext); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "persist-mode"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "parameter-source-factory"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "use-payload-as-parameter-source"); + + + final BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition(); + final String jpaExecutorBeanName = BeanDefinitionReaderUtils.generateBeanName(jpaExecutorBuilderBeanDefinition, parserContext.getRegistry()); + + parserContext.registerBeanComponent(new BeanComponentDefinition(jpaExecutorBuilderBeanDefinition, jpaExecutorBeanName)); + + final BeanDefinitionBuilder jpaOutboundChannelAdapterBuilder = BeanDefinitionBuilder.genericBeanDefinition(JpaOutboundGatewayFactoryBean.class); + jpaOutboundChannelAdapterBuilder.addConstructorArgReference(jpaExecutorBeanName); + jpaOutboundChannelAdapterBuilder.addPropertyValue("producesReply", Boolean.FALSE); + + final Element transactionalElement = DomUtils.getChildElementByTagName(element, "transactional"); + + if(transactionalElement != null) { + BeanDefinition txAdviceDefinition = JpaParserUtils.configureTransactionAttributes(transactionalElement); + ManagedList adviceChain = new ManagedList(); + adviceChain.add(txAdviceDefinition); + jpaOutboundChannelAdapterBuilder.addPropertyValue("adviceChain", adviceChain); + } + + return jpaOutboundChannelAdapterBuilder.getBeanDefinition(); + + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParser.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParser.java new file mode 100644 index 0000000000..6ba73453e8 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParser.java @@ -0,0 +1,99 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; +import org.springframework.beans.factory.support.ManagedList; +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.jpa.outbound.JpaOutboundGatewayFactoryBean; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * The Parser for JPA Outbound Gateway, the MessageHandler implementation is same as the + * outbound chanel adapter and hence we extend the class and setting the few additional + * attributes that we wish to in the MessageSource + * + * @author Amol Nayak + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public class JpaOutboundGatewayParser extends AbstractConsumerEndpointParser { + + protected boolean shouldGenerateId() { + return false; + } + + protected boolean shouldGenerateIdAsFallback() { + return true; + } + + protected BeanDefinitionBuilder parseHandler(Element gatewayElement, ParserContext parserContext) { + + final BeanDefinitionBuilder jpaExecutorBuilder = JpaParserUtils.getJpaExecutorBuilder(gatewayElement, parserContext); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "persist-mode"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "parameter-source-factory"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "use-payload-as-parameter-source"); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-after-poll"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "delete-per-row"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, gatewayElement, "expect-single-result"); + + final BeanDefinition jpaExecutorBuilderBeanDefinition = jpaExecutorBuilder.getBeanDefinition(); + final String jpaExecutorBeanName = BeanDefinitionReaderUtils.generateBeanName(jpaExecutorBuilderBeanDefinition, parserContext.getRegistry()); + + parserContext.registerBeanComponent(new BeanComponentDefinition(jpaExecutorBuilderBeanDefinition, jpaExecutorBeanName)); + + final BeanDefinitionBuilder jpaOutboundGatewayBuilder = BeanDefinitionBuilder + .genericBeanDefinition(JpaOutboundGatewayFactoryBean.class); + + jpaOutboundGatewayBuilder.addConstructorArgReference(jpaExecutorBeanName); + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaOutboundGatewayBuilder, gatewayElement, "gateway-type"); + + final String replyChannel = gatewayElement.getAttribute("reply-channel"); + + if (StringUtils.hasText(replyChannel)) { + jpaOutboundGatewayBuilder.addPropertyReference("outputChannel", replyChannel); + } + + final Element transactionalElement = DomUtils.getChildElementByTagName(gatewayElement, "transactional"); + + if(transactionalElement != null) { + BeanDefinition txAdviceDefinition = JpaParserUtils.configureTransactionAttributes(transactionalElement); + ManagedList adviceChain = new ManagedList(); + adviceChain.add(txAdviceDefinition); + jpaOutboundGatewayBuilder.addPropertyValue("adviceChain", adviceChain); + } + + return jpaOutboundGatewayBuilder; + + } + + protected String getInputChannelAttributeName() { + return "request-channel"; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaParserUtils.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaParserUtils.java new file mode 100644 index 0000000000..ddbb2bb802 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/JpaParserUtils.java @@ -0,0 +1,206 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.TypedStringValue; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.ManagedList; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.integration.config.xml.IntegrationNamespaceUtils; +import org.springframework.integration.endpoint.AbstractPollingEndpoint; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.util.xml.DomUtils; +import org.w3c.dom.Element; + +/** + * The common method for generating the BeanDefinition for the common MessageHandler + * is implemented in this class + * + * @author Amol Nayak + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public final class JpaParserUtils { + + private static final Log logger = LogFactory.getLog(JpaParserUtils.class); + + /** Prevent instantiation. */ + private JpaParserUtils() { + throw new AssertionError(); + } + + /** + * Create a new {@link BeanDefinitionBuilder} for the class {@link JpaExecutor}. + * Initialize the wrapped {@link JpaExecutor} with common properties. + * + * @param element Must not be Null + * @param parserContext Must not be Null + * @return The BeanDefinitionBuilder for the JpaExecutor + */ + public static BeanDefinitionBuilder getJpaExecutorBuilder(final Element element, + final ParserContext parserContext) { + + Assert.notNull(element, "The provided element must not be Null."); + Assert.notNull(parserContext, "The provided parserContext must not be Null."); + + final Object source = parserContext.extractSource(element); + + final BeanDefinitionBuilder jpaExecutorBuilder = BeanDefinitionBuilder.genericBeanDefinition(JpaExecutor.class); + + int attributeCount = 0; + + final String entityManagerRef = element.getAttribute("entity-manager"); + final String entityManagerFactoryRef = element.getAttribute("entity-manager-factory"); + final String jpaOperationsRef = element.getAttribute("jpa-operations"); + + if (StringUtils.hasText(jpaOperationsRef)) { + attributeCount++; + jpaExecutorBuilder.addConstructorArgReference(jpaOperationsRef); + } + + if (StringUtils.hasText(entityManagerRef)) { + + if (attributeCount > 0) { + parserContext.getReaderContext().error("Exactly only one of the attributes 'entity-manager' or " + + "'entity-manager-factory' or 'jpa-operations' must be be set.", source); + } + + attributeCount++; + jpaExecutorBuilder.addConstructorArgReference(entityManagerRef); + } + + if (StringUtils.hasText(entityManagerFactoryRef)) { + + if (attributeCount > 0) { + parserContext.getReaderContext().error("Exactly only one of the attributes 'entity-manager' or " + + "'entity-manager-factory' or 'jpa-operations' must be be set.", source); + } + + attributeCount++; + jpaExecutorBuilder.addConstructorArgReference(entityManagerFactoryRef); + } + + if (attributeCount == 0) { + parserContext.getReaderContext().error("Exactly one of the attributes 'entity-manager' or " + + "'entity-manager-factory' or 'jpa-operations' must be be set.", source); + } + + final ManagedList jpaParameterList = JpaParserUtils.getJpaParameterBeanDefinitions(element, parserContext); + + if (!jpaParameterList.isEmpty()) { + jpaExecutorBuilder.addPropertyValue("jpaParameters", jpaParameterList); + } + + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "entity-class"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "jpa-query"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "native-query"); + IntegrationNamespaceUtils.setValueIfAttributeDefined(jpaExecutorBuilder, element, "named-query"); + + return jpaExecutorBuilder; + + } + + /** + * @param jpaComponent + * @param parserContext + */ + public static ManagedList getJpaParameterBeanDefinitions( + Element jpaComponent, ParserContext parserContext) { + + final ManagedList parameterList = new ManagedList(); + + final List parameterChildElements = DomUtils + .getChildElementsByTagName(jpaComponent, "parameter"); + + for (Element childElement : parameterChildElements) { + + final BeanDefinitionBuilder parameterBuilder = BeanDefinitionBuilder.genericBeanDefinition(JpaParameter.class); + + String name = childElement.getAttribute("name"); + String expression = childElement.getAttribute("expression"); + String value = childElement.getAttribute("value"); + String type = childElement.getAttribute("type"); + + if (StringUtils.hasText(name)) { + parameterBuilder.addPropertyValue("name", name); + } + + if (StringUtils.hasText(expression)) { + parameterBuilder.addPropertyValue("expression", expression); + } + + if (StringUtils.hasText(value)) { + + if (!StringUtils.hasText(type)) { + + if (logger.isInfoEnabled()) { + logger.info(String + .format("Type attribute not set for parameter '%s'. Defaulting to " + + "'java.lang.String'.", value)); + } + + parameterBuilder.addPropertyValue("value", + new TypedStringValue(value, String.class)); + + } else { + parameterBuilder.addPropertyValue("value", + new TypedStringValue(value, type)); + } + + } + + parameterList.add(parameterBuilder.getBeanDefinition()); + } + + return parameterList; + + } + + /** + * Parse a "transactional" element and configure a TransactionInterceptor with "transactionManager" + * and other "transactionDefinition" properties. This advisor will be applied on the Polling Task proxy + * (see {@link AbstractPollingEndpoint}). + * + * FIXME Copied from {@link org.springframework.integration.config.xml.PollerParser} - should be refactored. + */ + public static BeanDefinition configureTransactionAttributes(Element txElement) { + BeanDefinitionBuilder txDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefaultTransactionAttribute.class); + txDefinitionBuilder.addPropertyValue("propagationBehaviorName", "PROPAGATION_" + txElement.getAttribute("propagation")); + txDefinitionBuilder.addPropertyValue("isolationLevelName", "ISOLATION_" + txElement.getAttribute("isolation")); + txDefinitionBuilder.addPropertyValue("timeout", txElement.getAttribute("timeout")); + txDefinitionBuilder.addPropertyValue("readOnly", txElement.getAttribute("read-only")); + BeanDefinitionBuilder attributeSourceBuilder = BeanDefinitionBuilder.genericBeanDefinition(MatchAlwaysTransactionAttributeSource.class); + attributeSourceBuilder.addPropertyValue("transactionAttribute", txDefinitionBuilder.getBeanDefinition()); + BeanDefinitionBuilder txInterceptorBuilder = BeanDefinitionBuilder.genericBeanDefinition(TransactionInterceptor.class); + txInterceptorBuilder.addPropertyReference("transactionManager", txElement.getAttribute("transaction-manager")); + txInterceptorBuilder.addPropertyValue("transactionAttributeSource", attributeSourceBuilder.getBeanDefinition()); + return txInterceptorBuilder.getBeanDefinition(); + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/package-info.java new file mode 100644 index 0000000000..6b8aded2ce --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/config/xml/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides parser classes to provide Xml namespace support for the Jpa components. + */ +package org.springframework.integration.jpa.config.xml; \ No newline at end of file diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java new file mode 100644 index 0000000000..3e98bb671d --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/AbstractJpaOperations.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.orm.jpa.SharedEntityManagerCreator; +import org.springframework.util.Assert; + +/** + * + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +abstract class AbstractJpaOperations implements JpaOperations, InitializingBean { + + protected EntityManager entityManager; + private EntityManagerFactory entityManagerFactory; + + + public void setEntityManager(EntityManager entityManager) { + Assert.notNull(entityManager, "The provided entitymanager must not be null."); + this.entityManager = entityManager; + } + + + public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) { + Assert.notNull(entityManagerFactory, "The provided entitymanagerFactory must not be null."); + this.entityManagerFactory = entityManagerFactory; + } + + public final void afterPropertiesSet() { + this.onInit(); + } + + /** + * Subclasses may implement this for initialization logic. + */ + + protected void onInit() { + + if (this.entityManager == null && this.entityManagerFactory != null) { + this.entityManager = SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory); + } + + Assert.notNull(entityManager, "The entitymanager is null. Please set " + + "either the entityManager or the entityManagerFactory."); + + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java new file mode 100644 index 0000000000..833e2b9fa8 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/DefaultJpaOperations.java @@ -0,0 +1,230 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import javax.persistence.Parameter; +import javax.persistence.Query; + +import org.springframework.integration.jpa.support.JpaUtils; +import org.springframework.integration.jpa.support.parametersource.ParameterSource; +import org.springframework.integration.jpa.support.parametersource.PositionSupportingParameterSource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Class similar to JPA template limited to the operations required for the JPA adapters/gateway + * not using JpaTemplate as the class is deprecated since Spring 3.1 + * + * @author Amol Nayak + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public class DefaultJpaOperations extends AbstractJpaOperations { + + public void delete(Object entity) { + Assert.notNull(entity, "The entity must not be null!"); + entityManager.remove(entity); + } + + public void deleteInBatch(Iterable entities) { + + Assert.notNull(entities, "entities must not be null."); + + Iterator iterator = entities.iterator(); + + if (!iterator.hasNext()) { + return; + } + + Class entityClass = null; + + for (Object object : entities) { + if (entityClass == null) { + entityClass = object.getClass(); + } else { + if (entityClass != object.getClass()) { + throw new IllegalArgumentException("entities must be of the same type."); + } + } + } + + final String entityName = JpaUtils.getEntityName(entityManager, entityClass); + final String queryString = JpaUtils.getQueryString(JpaUtils.DELETE_ALL_QUERY_STRING, entityName); + + JpaUtils.applyAndBind(queryString, entities, entityManager) + .executeUpdate(); + + } + + public int executeUpdate(String updateQuery, ParameterSource source) { + Query query = entityManager.createQuery(updateQuery); + setParametersIfRequired(updateQuery, source, query); + return query.executeUpdate(); + } + + public int executeUpdateWithNamedQuery(String updateQuery, ParameterSource source) { + Query query = entityManager.createNamedQuery(updateQuery); + setParametersIfRequired(updateQuery, source, query); + return query.executeUpdate(); + } + + public int executeUpdateWithNativeQuery(String updateQuery, ParameterSource source) { + Query query = entityManager.createNativeQuery(updateQuery); + setParametersIfRequired(updateQuery, source, query); + return query.executeUpdate(); + } + + public T find(Class entityType, Object id) { + return entityManager.find(entityType, id); + } + + private Query getQuery(String queryString, ParameterSource source) { + Query query = entityManager.createQuery(queryString); + setParametersIfRequired(queryString, source, query); + return query; + } + + public List getResultListForClass(Class entityClass, int maxNumberOfResults) { + + final String entityName = JpaUtils.getEntityName(entityManager, entityClass); + final Query query = entityManager.createQuery("select x from " + entityName + " x", entityClass); + + if(maxNumberOfResults > 0) { + query.setMaxResults(maxNumberOfResults); + } + + return query.getResultList(); + + } + + public List getResultListForNamedQuery(String selectNamedQuery, + ParameterSource parameterSource, int maxNumberOfResults) { + + final Query query = entityManager.createNamedQuery(selectNamedQuery); + setParametersIfRequired(selectNamedQuery, parameterSource, query); + + if(maxNumberOfResults > 0) { + query.setMaxResults(maxNumberOfResults); + } + + return query.getResultList(); + + } + + public List getResultListForNativeQuery(String selectQuery, Class entityClass, + ParameterSource parameterSource, int maxNumberOfResults) { + + final Query query; + + if (entityClass == null) { + query = entityManager.createNativeQuery(selectQuery); + } else { + query = entityManager.createNativeQuery(selectQuery, entityClass); + } + + setParametersIfRequired(selectQuery, parameterSource, query); + + if(maxNumberOfResults > 0) { + query.setMaxResults(maxNumberOfResults); + } + + return query.getResultList(); + } + + public List getResultListForQuery(String query, ParameterSource source) { + return getResultListForQuery(query,source, 0); + } + + public List getResultListForQuery(String queryString, ParameterSource source, + int maxNumberOfResults) { + + Query query = getQuery(queryString,source); + + if(maxNumberOfResults > 0) { + query.setMaxResults(maxNumberOfResults); + } + + return query.getResultList(); + } + + public Object getSingleResultForQuery(String queryString, ParameterSource source) { + Query query = getQuery(queryString,source); + return query.getSingleResult(); + } + + public Object merge(Object entity) { + return entityManager.merge(entity); + } + + public void persist(Object entity) { + entityManager.persist(entity); + } + + /** + * Given a JPQL query, this method gets all parameters defined in this query and + * use the {@link JPAQLParameterSource} to find their values and set them + * + */ + private void setParametersIfRequired(String queryString, + ParameterSource source, Query query) { + Set> parameters = query.getParameters(); + + if(parameters != null && !parameters.isEmpty()) { + if(source != null) { + for(Parameter param:parameters) { + String paramName = param.getName(); + Integer position = param.getPosition(); + + final Object paramValue; + + if (position != null) { + + if (source instanceof PositionSupportingParameterSource) { + paramValue = ((PositionSupportingParameterSource) source).getValueByPosition(position - 1); + query.setParameter(position, paramValue); + } else { + throw new JpaOperationFailedException("Positional Parameters are only support " + + "for PositionSupportingParameterSources.") + .withOffendingJPAQl(queryString); + } + + } else { + + if(StringUtils.hasText(paramName)) { + paramValue = source.getValue(paramName); + query.setParameter(paramName, paramValue); + } else { + throw new JpaOperationFailedException( + "This parameter does not contain a parameter name. " + + "Additionally it is not a postitional parameter, neither.") + .withOffendingJPAQl(queryString); + } + } + + } + } else { + throw new IllegalArgumentException("Query has parameters but no parameter source provided"); + } + + } + } +} 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 new file mode 100644 index 0000000000..a04db2df47 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaExecutor.java @@ -0,0 +1,453 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.integration.Message; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.jpa.support.parametersource.BeanPropertyParameterSourceFactory; +import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory; +import org.springframework.integration.jpa.support.parametersource.ParameterSource; +import org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory; +import org.springframework.util.Assert; + +/** + * Executes Jpa Operations that produce payload objects from the result of the provided: + * + *
    + *
  • entityClass
  • + *
  • JpQl Select Query
  • + *
  • Sql Native Query
  • + *
  • JpQl Named Query
  • + *
  • Sql Native Named Query
  • + *
+ * + * When objects are being retrieved, it also possibly to: + * + *
    + *
  • delete the retrieved object
  • + *
+ * + * If neither entityClass nor any other query is specified then the entity-class + * is "guessed" from the {@link Message} payload. + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaExecutor implements InitializingBean { + + private volatile JpaOperations jpaOperations; + private volatile List jpaParameters; + + private volatile Class entityClass; + private volatile String jpaQuery; + private volatile String nativeQuery; + private volatile String namedQuery; + + /** 0 means all possible objects shall be retrieved. */ + private volatile int maxNumberOfResults = 0; + + private volatile PersistMode persistMode = PersistMode.MERGE; + + private volatile ParameterSourceFactory parameterSourceFactory = null; + private volatile ParameterSource parameterSource; + + private volatile boolean deleteAfterPoll = false; + private volatile boolean deletePerRow = false; + + private volatile boolean expectSingleResult = false; + + /** + * 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 + * used for the sqlParameterSourceFactory property. + */ + private volatile Boolean usePayloadAsParameterSource = null; + + //~~~~Constructors~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * Constructor taking an {@link EntityManagerFactory} from which the + * {@link EntityManager} can be obtained. + * + * @param entityManagerFactory Must not be null. + */ + public JpaExecutor(EntityManagerFactory entityManagerFactory) { + Assert.notNull(entityManagerFactory, "entityManagerFactory must not be null."); + + DefaultJpaOperations defaultJpaOperations = new DefaultJpaOperations(); + defaultJpaOperations.setEntityManagerFactory(entityManagerFactory); + defaultJpaOperations.afterPropertiesSet(); + + this.jpaOperations = defaultJpaOperations; + } + + /** + * Constructor taking an {@link EntityManager} directly. + * + * @param entityManager Must not be null. + */ + public JpaExecutor(EntityManager entityManager) { + Assert.notNull(entityManager, "entityManager must not be null."); + + DefaultJpaOperations defaultJpaOperations = new DefaultJpaOperations(); + defaultJpaOperations.setEntityManager(entityManager); + defaultJpaOperations.afterPropertiesSet(); + this.jpaOperations = defaultJpaOperations; + } + + /** + * If custom behavior is required a custom implementation of {@link JpaOperations} + * can be passed in. The implementations themselves typically provide access + * to the {@link EntityManager}. + * + * See also {@link DefaultJpaOperations} and {@link AbstractJpaOperations}. + * + * @param jpaOperations Must not be null. + */ + public JpaExecutor(JpaOperations jpaOperations) { + Assert.notNull(jpaOperations, "jpaOperations must not be null."); + this.jpaOperations = jpaOperations; + } + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + /** + * + * Verifies and sets the parameters. E.g. initializes the to be used + * {@link ParameterSourceFactory}. + * + */ + public void afterPropertiesSet() { + + if (this.jpaParameters != null ) { + + if (this.parameterSourceFactory == null) { + ExpressionEvaluatingParameterSourceFactory expressionSourceFactory = + new ExpressionEvaluatingParameterSourceFactory(); + expressionSourceFactory.setParameters(jpaParameters); + this.parameterSourceFactory = expressionSourceFactory; + + } else { + + if (!(this.parameterSourceFactory instanceof ExpressionEvaluatingParameterSourceFactory)) { + throw new IllegalStateException("You are providing 'JpaParameters'. " + + "Was expecting the the provided jpaParameterSourceFactory " + + "to be an instance of 'ExpressionEvaluatingJpaParameterSourceFactory', " + + "however the provided one is of type '" + this.parameterSourceFactory.getClass().getName() + "'"); + } + + } + + if (this.usePayloadAsParameterSource == null) { + this.usePayloadAsParameterSource = false; + } + + } else { + + if (this.parameterSourceFactory == null) { + this.parameterSourceFactory = new BeanPropertyParameterSourceFactory(); + } + + if (this.usePayloadAsParameterSource == null) { + this.usePayloadAsParameterSource = true; + } + + } + + } + + /** + * Executes the actual Jpa Operation. Call this method, if you need access to + * process return values. This methods return a Map that contains either + * the number of affected entities or the affected entity itself. + * + * Keep in mind that the number of entities effected by the operation may + * 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. + */ + public Object executeOutboundJpaOperation(final Message message) { + + final Object result; + + if (this.jpaQuery != null) { + + result = this.jpaOperations.executeUpdate(this.jpaQuery, parameterSourceFactory.createParameterSource(message)); + + } else if (this.nativeQuery != null) { + + result = this.jpaOperations.executeUpdateWithNativeQuery(this.nativeQuery, parameterSourceFactory.createParameterSource(message)); + + } else if (this.namedQuery != null) { + + result = this.jpaOperations.executeUpdateWithNamedQuery(this.namedQuery, parameterSourceFactory.createParameterSource(message)); + + } else { + + if (PersistMode.PERSIST.equals(this.persistMode)) { + this.jpaOperations.persist(message.getPayload()); + result = message.getPayload(); + } else if (PersistMode.MERGE.equals(this.persistMode)) { + final Object mergedEntity = this.jpaOperations.merge(message.getPayload()); + result = mergedEntity; + } else if (PersistMode.DELETE.equals(this.persistMode)) { + this.jpaOperations.delete(message.getPayload()); + result = message.getPayload(); + } else { + throw new IllegalStateException(String.format("Unsupported PersistMode: '%s'", this.persistMode.name())); + } + + } + + return result; + + } + + /** + * Execute a (typically retrieving) JPA operation. The requestMessage + * can be used to provide additional query parameters using + * {@link JpaExecutor#parameterSourceFactorymeterSourceFactory}. If the + * requestMessage parameter is null then + * {@link JpaExecutor#parameterSource} is being used for providing query parameters. + * + * @param requestMessage May be null. + * @return The payload object, which may be null. + */ + @SuppressWarnings("unchecked") + public Object poll(final Message requestMessage) { + + final Object payload; + + final List result; + + if (requestMessage == null) { + result = doPoll(this.parameterSource); + } else { + result = doPoll(this.parameterSourceFactory.createParameterSource(requestMessage)); + } + + if (result.isEmpty()) { + payload = null; + } else { + + if (this.expectSingleResult) { + if (result.size() == 1) { + payload = result.iterator().next(); + } else { + + throw new MessageHandlingException(requestMessage, + "The Jpa operation returned more than " + + "1 result object but expectSingleResult was 'true'."); + } + + } else { + payload = result; + } + + } + + if (payload != null && this.deleteAfterPoll) { + + if (payload instanceof Iterable) { + if (this.deletePerRow) { + for (Object entity : (Iterable) payload) { + this.jpaOperations.delete(entity); + } + } else { + this.jpaOperations.deleteInBatch((Iterable) payload); + } + } else { + this.jpaOperations.delete(payload); + } + + } + + return payload; + } + + /** + * Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}. + */ + public Object poll() { + return poll(null); + } + + protected List doPoll(ParameterSource jpaQLParameterSource) { + + List payload = null; + + if (this.jpaQuery != null) { + payload = jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource, maxNumberOfResults); + } else if (this.nativeQuery != null) { + payload = jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource, maxNumberOfResults); + } else if (this.namedQuery != null) { + payload = jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource, maxNumberOfResults); + } else if (this.entityClass != null) { + payload = jpaOperations.getResultListForClass(this.entityClass, maxNumberOfResults); + } else { + throw new IllegalStateException("For the polling operation, one of " + + "the following properties must be specified: " + + "query, namedQuery or entityClass."); + } + + return payload; + } + + //~~~~Setters~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + /** + * Sets the class type which is being used to poll the database or to also + * update the persistence store. + * + * @param entityClass Must not be null. + */ + public void setEntityClass(Class entityClass) { + Assert.notNull(entityClass, "entityClass must not be null."); + this.entityClass = entityClass; + } + + /** + * @param jpaQuery The provided JPA query must neither be null nor empty. + */ + public void setJpaQuery(String jpaQuery) { + + if (this.nativeQuery != null || this.namedQuery != null) { + throw new IllegalArgumentException("You can define only one of the " + + "properties 'jpaQuery', 'nativeQuery', 'namedQuery'"); + } + + Assert.hasText(jpaQuery, "jpaQuery must neither be null nor empty."); + + this.jpaQuery = jpaQuery; + } + + /** + * You can also use native Sql queries to poll data from the database. If set + * this property will allow you to use native SQL. Optionally you can also set + * the entityClass property at the same time. If specified the entityClass will + * be used as the result class for the native query. + * + * @param nativeQuery The provided SQL query must neither be null nor empty. + */ + public void setNativeQuery(String nativeQuery) { + + if (this.jpaQuery != null || this.namedQuery != null) { + throw new IllegalArgumentException("You can define only one of the " + + "properties 'jpaQuery', 'nativeQuery', 'namedQuery'"); + } + + Assert.hasText(nativeQuery, "nativeQuery must neither be null nor empty."); + + this.nativeQuery = nativeQuery; + } + + /** + * A named query can either refer to a named JPQL based query or a native SQL + * query. + * + * @param namedQuery Must neither be null nor empty + */ + public void setNamedQuery(String namedQuery) { + + if (this.jpaQuery != null || this.nativeQuery != null) { + throw new IllegalArgumentException("You can define only one of the " + + "properties 'jpaQuery', 'nativeQuery', 'namedQuery'"); + } + + Assert.hasText(namedQuery, "namedQuery must neither be null nor empty."); + this.namedQuery = namedQuery; + } + + public void setPersistMode(PersistMode persistMode) { + this.persistMode = persistMode; + } + + public void setJpaParameters(List jpaParameters) { + this.jpaParameters = jpaParameters; + } + + public void setUsePayloadAsParameterSource(Boolean usePayloadAsParameterSource) { + this.usePayloadAsParameterSource = usePayloadAsParameterSource; + } + + /** + * If not set, this property default to 'true', which means that deletion + * occur on a per object basis. + * + * If set to 'false' the elements of the payload are deleted as a batch + * operation. Be aware that this exhibit issues in regards to cascaded deletes. //TODO further information needed + * + * @param deletePerRow Defaults to 'true'. + */ + public void setDeletePerRow(boolean deletePerRow) { + this.deletePerRow = deletePerRow; + } + + /** + * If set to 'true', the retrieved objects are deleted from the database upon + * being polled. May not work in all situations, e.g. for Native SQL Queries. + * + * @param deleteAfterPoll Defaults to 'false'. + */ + public void setDeleteAfterPoll(boolean deleteAfterPoll) { + this.deleteAfterPoll = deleteAfterPoll; + } + + /** + * + * @param maxNumberOfResults Must not be negative. + */ + public void setMaxRows(int maxNumberOfResults) { + Assert.isTrue(maxNumberOfResults >= 0, "maxRows must not be negative."); + this.maxNumberOfResults = maxNumberOfResults; + } + + /** + * + * @param parameterSourceFactory + */ + public void setParameterSourceFactory( + ParameterSourceFactory parameterSourceFactory) { + Assert.notNull(parameterSourceFactory, "parameterSourceFactory must not be null."); + this.parameterSourceFactory = parameterSourceFactory; + } + + /** + * + * @param parameterSource + */ + public void setParameterSource(ParameterSource parameterSource) { + Assert.notNull(parameterSource, "parameterSource must not be null."); + this.parameterSource = parameterSource; + } + + public void setExpectSingleResult(boolean expectSingleResult) { + this.expectSingleResult = expectSingleResult; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperationFailedException.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperationFailedException.java new file mode 100644 index 0000000000..9c9bdf7f18 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperationFailedException.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +/** + * An Exception that would be thrown if any of the Operations from {@link JpaOperations} fails + * + * @author Amol Nayak + * @since 2.2 + * + */ +public class JpaOperationFailedException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private String offendingJPAQl; + + public JpaOperationFailedException() { + super(); + } + + public JpaOperationFailedException(String message, Throwable cause) { + super(message, cause); + } + + public JpaOperationFailedException(String message) { + super(message); + } + + public JpaOperationFailedException(Throwable cause) { + super(cause); + } + + /** + * If execution of a JPA QL fails, we can set that query using this convenience method. + * @param offendingJPAQl + * @return JpaOperationFailedException + */ + public JpaOperationFailedException withOffendingJPAQl(String offendingJPAQl) { + setOffendingJPAQl(offendingJPAQl); + return this; + } + + public String getOffendingJPAQl() { + return offendingJPAQl; + } + + public void setOffendingJPAQl(String offendingJPAQl) { + this.offendingJPAQl = offendingJPAQl; + } +} 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 new file mode 100644 index 0000000000..007cd2c8f2 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/JpaOperations.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.util.List; + +import org.springframework.integration.jpa.support.parametersource.ParameterSource; + +/** + * The Interface containing all the JpaOperations those will be executed by + * the Jpa Spring Integration components. + * + * @author Amol Nayak + * @author Gunnar Hillert + * @since 2.2 + * + */ +public interface JpaOperations { + + /** + * + * @param entity + */ + void delete(Object entity); + + /** + * + * @param entities + */ + void deleteInBatch(Iterable entities); + + /** + * Executes the given update statement and uses the given parameter source to + * set the required query parameters. + * + * @param updateQuery Must Not be empty. + * @param source Must Not be null. + * @return The number of entities updated + */ + int executeUpdate(String updateQuery, ParameterSource source); + + + /** + * + * @param updateQuery + * @param source + * @return The number of entities updated + */ + int executeUpdateWithNamedQuery(String updateQuery, ParameterSource source); + + /** + * + * @param updateQuery + * @param source + * @return The number of entities updated + */ + int executeUpdateWithNativeQuery(String updateQuery, ParameterSource source); + + + /** + * Find an Entity of given type with the given primary key type. + * + * @param + * @param entityType + * @param id + * @return The entity if it exist, null is returned otherwise + */ + T find(Class entityType, Object id); + + /** + * + * @param entityClass + * @param maxNumberOfReturnedObjects + * @return List of found entities + */ + List getResultListForClass(Class entityClass, + int maxNumberOfReturnedObjects); + + /** + * + * @param selectNamedQuery + * @param jpaQLParameterSource + * @param maxNumberOfResults + * @return List of found entities + */ + List getResultListForNamedQuery(String selectNamedQuery, ParameterSource jpaQLParameterSource, + int maxNumberOfResults); + + /** + * + * @param selectQuery + * @param entityClass + * @param jpaQLParameterSource + * @param maxNumberOfResults + * @return List of found entities + */ + List getResultListForNativeQuery(String selectQuery, + Class entityClass, ParameterSource jpaQLParameterSource, int maxNumberOfResults); + + /** + * Executes the provided query to return a list of results + * @param query + * @param source the Parameter source for this query to be executed, if none then set as null + * @return List of found entities + */ + List getResultListForQuery(String query, ParameterSource source); + + /** + * Executes the provided query to return a list of results. + * + * @param query Must not be null or empty + * @param maxNumberOfResults Must be a non-negative value + * @param source the Parameter source for this query to be executed, if none then set null + * @return List of found entities + */ + List getResultListForQuery(String query, ParameterSource source, int maxNumberOfResults); + + /** + * Executes the provided query to return a single element + * + * @param query Must not be empty + * @param source the Parameter source for this query to be executed, if none then set as null + * @return Will always return a result. If no object was found in the database an exception is raised. + */ + Object getSingleResultForQuery(String query, ParameterSource source); + + /** + * The entity to be merged with the entity manager + * + * @param entity Must not be null. + * @return The merged managed instance of the entity. + */ + Object merge(Object entity); + + /** + * Persists the entity + * @param entity Must not be null + * + */ + void persist(Object entity); + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/package-info.java new file mode 100644 index 0000000000..bd2ad18331 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/core/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides core classes of the JPA module. + */ +package org.springframework.integration.jpa.core; \ No newline at end of file diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapter.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapter.java new file mode 100644 index 0000000000..e8e90b388d --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-2012 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.jpa.inbound; + +import org.springframework.integration.Message; +import org.springframework.integration.context.IntegrationObjectSupport; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; + +/** + * Polling message source that produces messages from the result of the provided: + * + *
    + *
  • entityClass
  • + *
  • JpQl Select Query
  • + *
  • Sql Native Query
  • + *
  • JpQl Named Query
  • + *
  • Sql Native Named Query
  • + *
+ * + * After the objects have been polled, it also possibly to either: + * + * executes an update after the select possibly to updated the state of selected records + * + *
    + *
  • executes an update (per retrieved object or for the entire payload)
  • + *
  • delete the retrieved object
  • + *
+ * + * @author Amol Nayak + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public class JpaPollingChannelAdapter extends IntegrationObjectSupport implements MessageSource{ + + private final JpaExecutor jpaExecutor; + + /** + * Constructor taking a {@link JpaExecutor} that provide all required JPA + * functionality. + * + * @param jpaExecutor Must not be null. + */ + public JpaPollingChannelAdapter(JpaExecutor jpaExecutor) { + super(); + Assert.notNull(jpaExecutor, "jpaExecutor must not be null."); + this.jpaExecutor = jpaExecutor; + } + + /** + * Check for mandatory attributes + */ + @Override + protected void onInit() throws Exception { + super.onInit(); + } + + /** + * Uses {@link JpaExecutor#poll()} to executes the JPA operation. + * + * If {@link JpaExecutor#poll()} returns null, this method will return + * null. Otherwise, a new {@link Message} is constructed and returned. + */ + public Message receive() { + + final Object payload = jpaExecutor.poll(); + + if (payload == null) { + return null; + } + + return MessageBuilder.withPayload(payload).build(); + } + + @Override + public String getComponentType(){ + return "jpa:inbound-channel-adapter"; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/package-info.java new file mode 100644 index 0000000000..f7b85c32a5 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/inbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides inbound Spring Integration Jpa components. + */ +package org.springframework.integration.jpa.inbound; \ No newline at end of file diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java new file mode 100644 index 0000000000..28fbce0be7 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGateway.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import org.springframework.integration.Message; +import org.springframework.integration.handler.AbstractReplyProducingMessageHandler; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.OutboundGatewayType; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.util.Assert; + +/** + * The Jpa Outbound Gateway will allow you to make outbound operations to either: + * + *
    + *
  • submit (insert, delete) data to a database using JPA
  • + *
  • retrieve (select) data from a database
  • + *
+ * + * Depending on the selected {@link OutboundGatewayType}, the outbound gateway + * will use either the {@link JpaExecutor}'s poll method or its + * executeOutboundJpaOperation method. + * + * In order to initialize the adapter, you must provide a {@link JpaExecutor} as + * constructor. + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaOutboundGateway extends AbstractReplyProducingMessageHandler { + + private final JpaExecutor jpaExecutor; + private OutboundGatewayType gatewayType = OutboundGatewayType.UPDATING; + private boolean producesReply = true; //false for outbound-channel-adapter, true for outbound-gateway + + /** + * Constructor taking an {@link JpaExecutor} that wraps all JPA Operations. + * + * @param jpaExecutor Must not be null + * + */ + public JpaOutboundGateway(JpaExecutor jpaExecutor) { + Assert.notNull(jpaExecutor, "jpaExecutor must not be null."); + this.jpaExecutor = jpaExecutor; + } + + /** + * + */ + @Override + protected void onInit() { + super.onInit(); + } + + @Override + protected Object handleRequestMessage(Message requestMessage) { + + final Object result; + + if (OutboundGatewayType.RETRIEVING.equals(this.gatewayType)) { + + result = this.jpaExecutor.poll(requestMessage); + + } else if (OutboundGatewayType.UPDATING.equals(this.gatewayType)) { + + result = this.jpaExecutor.executeOutboundJpaOperation(requestMessage); + + } else { + + throw new IllegalArgumentException(String.format("GatewayType '%s' is not supported.", this.gatewayType)); + + } + + if (result == null || !producesReply) { + return null; + } + + return MessageBuilder.withPayload(result).copyHeaders(requestMessage.getHeaders()).build(); + + } + + /** + * + * @param gatewayType + */ + public void setGatewayType(OutboundGatewayType gatewayType) { + Assert.notNull(gatewayType, "gatewayType must not be null."); + this.gatewayType = gatewayType; + } + + /** + * If set to 'false', this component will act as an Outbound Channel Adapter. + * If not explicitly set this property will default to 'true'. + * + * @param producesReply Defaults to 'true'. + * + */ + public void setProducesReply(boolean producesReply) { + this.producesReply = producesReply; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java new file mode 100644 index 0000000000..a27d029a1e --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayFactoryBean.java @@ -0,0 +1,116 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import java.util.List; + +import org.aopalliance.aop.Advice; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.core.MessageHandler; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.OutboundGatewayType; +import org.springframework.transaction.interceptor.TransactionInterceptor; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; + +/** + * The {@link JpaOutboundGatewayFactoryBean} creates instances of the + * {@link JpaOutboundGateway}. Optionally this {@link FactoryBean} will add Aop + * Advices (e.g. {@link TransactionInterceptor} to the {@link JpaOutboundGateway} + * instance. + * + * @author Amol Nayak + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaOutboundGatewayFactoryBean extends AbstractFactoryBean { + + private final JpaExecutor jpaExecutor; + private OutboundGatewayType gatewayType = OutboundGatewayType.UPDATING; + + private volatile List adviceChain; + private volatile ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); + private boolean producesReply = true; + private MessageChannel outputChannel; + private int order; + + /** + * Constructor taking an {@link JpaExecutor} that wraps all JPA Operations. + * + * @param jpaExecutor Must not be null + * + */ + public JpaOutboundGatewayFactoryBean(JpaExecutor jpaExecutor) { + Assert.notNull(jpaExecutor, "jpaExecutor must not be null."); + this.jpaExecutor = jpaExecutor; + } + + public void setGatewayType(OutboundGatewayType gatewayType) { + this.gatewayType = gatewayType; + } + + @Override + public Class getObjectType() { + return MessageHandler.class; + } + + @Override + protected MessageHandler createInstance() { + + JpaOutboundGateway jpaOutboundGateway = new JpaOutboundGateway(jpaExecutor); + jpaOutboundGateway.setGatewayType(this.gatewayType); + jpaOutboundGateway.setProducesReply(this.producesReply); + jpaOutboundGateway.setOutputChannel(this.outputChannel); + jpaOutboundGateway.setOrder(this.order); + + if (!CollectionUtils.isEmpty(this.adviceChain)) { + + ProxyFactory proxyFactory = new ProxyFactory(jpaOutboundGateway); + if (!CollectionUtils.isEmpty(adviceChain)) { + for (Advice advice : adviceChain) { + proxyFactory.addAdvice(advice); + } + } + + return (MessageHandler) proxyFactory.getProxy(this.beanClassLoader); + } + + return jpaOutboundGateway; + } + + public void setAdviceChain(List adviceChain) { + this.adviceChain = adviceChain; + } + + public void setProducesReply(boolean producesReply) { + this.producesReply = producesReply; + } + + public void setOutputChannel(MessageChannel outputChannel) { + this.outputChannel = outputChannel; + } + + public void setOrder(int order) { + this.order = order; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/package-info.java new file mode 100644 index 0000000000..a4e8ef5489 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/outbound/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides Spring Integration components for doing outbound operations. + */ +package org.springframework.integration.jpa.outbound; \ No newline at end of file diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/package-info.java new file mode 100644 index 0000000000..1102b80178 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/package-info.java @@ -0,0 +1,4 @@ +/** + * Root package of the JPA Module. + */ +package org.springframework.integration.jpa; \ No newline at end of file diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaParameter.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaParameter.java new file mode 100644 index 0000000000..4cdd9739ca --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaParameter.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2012 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.jpa.support; + +import org.springframework.util.Assert; + +/** + * Abstraction of Jpa parameters allowing to provide static parameters + * and SpEl Expression based parameters. + * + * TODO Should we combine ProcedureParameter class and this class? + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaParameter { + + private String name; + private Object value; + private String expression; + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + public Object getValue() { + return this.value; + } + public void setValue(Object value) { + this.value = value; + } + public String getExpression() { + return this.expression; + } + public void setExpression(String expression) { + this.expression = expression; + } + + /** + * Instantiates a new Jpa Parameter. + * + * @param name Name of the JPA parameter, must not be null or empty + * @param value If null, the expression property must be set + * @param expression If null, the value property must be set + */ + public JpaParameter(String name, Object value, String expression) { + super(); + + Assert.hasText(name, "'name' must not be empty."); + + this.name = name; + this.value = value; + this.expression = expression; + } + + /** + * Instantiates a new Jpa Parameter without a name. This is useful for specifying + * positional Jpa parameters. + * + * @param value If null, the expression property must be set + * @param expression If null, the value property must be set + */ + public JpaParameter(Object value, String expression) { + super(); + this.value = value; + this.expression = expression; + } + + /** + * Default constructor. + */ + public JpaParameter() { + super(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("JpaParameter [name=").append(this.name) + .append(", value=").append(this.value) + .append(", expression=").append(this.expression) + .append("]"); + return builder.toString(); + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaUtils.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaUtils.java new file mode 100644 index 0000000000..00248b75ea --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/JpaUtils.java @@ -0,0 +1,153 @@ +/* + * Copyright 2002-2012 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.jpa.support; + +import static java.util.regex.Pattern.CASE_INSENSITIVE; +import static java.util.regex.Pattern.compile; + +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.persistence.EntityManager; +import javax.persistence.Query; + +import org.springframework.util.Assert; + +/** + * This Utility contains a sub-set of utility methods from the Spring Data JPA Project. + * As the Spring Integration JPA adapter uses only these utility methods, they + * were copied into this class in order to prevent having to declare a dependency + * on Spring Data JPA. + * + * + * @author Oliver Gierke + * @author Gunnar Hillert + * + * @since 2.2 + * + */ +public final class JpaUtils { + + public static final String DELETE_ALL_QUERY_STRING = "delete from %s x"; + + private static final Pattern ALIAS_MATCH; + + private static final String IDENTIFIER = "[\\p{Alnum}._$]+"; + private static final String IDENTIFIER_GROUP = String.format("(%s)", IDENTIFIER); + + static { + + StringBuilder builder = new StringBuilder(); + builder.append("(?<=from)"); // from as starting delimiter + builder.append("(?: )+"); // at least one space separating + builder.append(IDENTIFIER_GROUP); // Entity name, can be qualified (any + builder.append("(?: as)*"); // exclude possible "as" keyword + builder.append("(?: )+"); // at least one space separating + builder.append("(\\w*)"); // the actual alias + + ALIAS_MATCH = compile(builder.toString(), CASE_INSENSITIVE); + + builder = new StringBuilder(); + builder.append("(select\\s+((distinct )?.+?)\\s+)?(from\\s+"); + builder.append(IDENTIFIER); + builder.append("(?:\\s+as)?\\s+)"); + builder.append(IDENTIFIER_GROUP); + builder.append("(.*)"); + + } + + /** + * Private constructor to prevent instantiation. + */ + private JpaUtils() { + + } + + + /** + * Resolves the alias for the entity to be retrieved from the given JPA query. + * + */ + public static String detectAlias(String query) { + + Matcher matcher = ALIAS_MATCH.matcher(query); + + return matcher.find() ? matcher.group(2) : null; + } + + /** + * Creates a where-clause referencing the given entities and appends it to the given query string. Binds the given + * entities to the query. + * + */ + public static Query applyAndBind(String queryString, Iterable entities, EntityManager entityManager) { + + Assert.notNull(queryString); + Assert.notNull(entities); + Assert.notNull(entityManager); + + Iterator iterator = entities.iterator(); + + if (!iterator.hasNext()) { + return entityManager.createQuery(queryString); + } + + String alias = detectAlias(queryString); + StringBuilder builder = new StringBuilder(queryString); + builder.append(" where"); + + int i = 0; + + while (iterator.hasNext()) { + + iterator.next(); + + builder.append(String.format(" %s = ?%d", alias, ++i)); + + if (iterator.hasNext()) { + builder.append(" or"); + } + } + + Query query = entityManager.createQuery(builder.toString()); + + iterator = entities.iterator(); + i = 0; + + while (iterator.hasNext()) { + query.setParameter(++i, iterator.next()); + } + + return query; + } + + /** + * Returns the query string for the given class name. + */ + public static String getQueryString(String template, String entityName) { + + Assert.hasText(entityName, "Entity name must not be null or empty!"); + + return String.format(template, entityName); + } + + public static String getEntityName(EntityManager em, Class entityClass) { + + return em.getMetamodel().entity(entityClass).getName(); + + } +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/OutboundGatewayType.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/OutboundGatewayType.java new file mode 100644 index 0000000000..3b22652bd0 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/OutboundGatewayType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2012 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.jpa.support; + +/** + * Indicates the mode of operation for the outbound Jpa Gateway. + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public enum OutboundGatewayType { + UPDATING, RETRIEVING +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/PersistMode.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/PersistMode.java new file mode 100644 index 0000000000..7974452d6e --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/PersistMode.java @@ -0,0 +1,27 @@ +/* + * Copyright 2002-2012 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.jpa.support; + +/** + * Indicates how entities shall be persisted to the underlying persistence store. + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public enum PersistMode { + PERSIST, MERGE, DELETE +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/package-info.java new file mode 100644 index 0000000000..8d5eeba52e --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various support classes used across Spring Integration Jpa Components. + */ +package org.springframework.integration.jpa.support; diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSource.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSource.java new file mode 100644 index 0000000000..2fbceef784 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSource.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +import java.beans.PropertyDescriptor; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.NotReadablePropertyException; +import org.springframework.beans.PropertyAccessor; +import org.springframework.beans.PropertyAccessorFactory; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class BeanPropertyParameterSource implements ParameterSource { + + private final BeanWrapper beanWrapper; + + private String[] propertyNames; + + + /** + * Create a new BeanPropertySqlParameterSource for the given bean. + * @param object the bean instance to wrap + */ + public BeanPropertyParameterSource(Object object) { + this.beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(object); + } + + + public boolean hasValue(String paramName) { + return this.beanWrapper.isReadableProperty(paramName); + } + + public Object getValue(String paramName) { + try { + return this.beanWrapper.getPropertyValue(paramName); + } + catch (NotReadablePropertyException ex) { + throw new IllegalArgumentException(ex.getMessage()); + } + } + + /** + * Provide access to the property names of the wrapped bean. + * Uses support provided in the {@link PropertyAccessor} interface. + * @return an array containing all the known property names + */ + public String[] getReadablePropertyNames() { + if (this.propertyNames == null) { + final List names = new ArrayList(); + PropertyDescriptor[] props = this.beanWrapper.getPropertyDescriptors(); + for (PropertyDescriptor pd : props) { + if (this.beanWrapper.isReadableProperty(pd.getName())) { + names.add(pd.getName()); + } + } + this.propertyNames = names.toArray(new String[names.size()]); + } + return this.propertyNames; + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSourceFactory.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSourceFactory.java new file mode 100644 index 0000000000..2a40040647 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/BeanPropertyParameterSourceFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class BeanPropertyParameterSourceFactory implements ParameterSourceFactory { + + private volatile Map staticParameters; + + public BeanPropertyParameterSourceFactory() { + this.staticParameters = Collections.unmodifiableMap(new HashMap()); + } + + /** + * If the input is a List or a Map, the output is a map parameter source, and in that case some static parameters + * can be added (default is empty). If the input is not a List or a Map then this value is ignored. + * + * @param staticParameters the static parameters to set + */ + public void setStaticParameters(Map staticParameters) { + this.staticParameters = staticParameters; + } + + public ParameterSource createParameterSource(Object input) { + ParameterSource toReturn = new StaticBeanPropertyParameterSource(input, staticParameters); + return toReturn; + } + + private static class StaticBeanPropertyParameterSource implements + ParameterSource { + + private final BeanPropertyParameterSource input; + + private final Map staticParameters; + + public StaticBeanPropertyParameterSource(Object input, Map staticParameters) { + this.input = new BeanPropertyParameterSource(input); + this.staticParameters = staticParameters; + } + + public Object getValue(String paramName) { + return staticParameters.containsKey(paramName) ? staticParameters.get(paramName) : input + .getValue(paramName); + } + + public boolean hasValue(String paramName) { + return staticParameters.containsKey(paramName) || input.hasValue(paramName); + } + + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSource.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSource.java new file mode 100644 index 0000000000..b3b2d154de --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSource.java @@ -0,0 +1,110 @@ +package org.springframework.integration.jpa.support.parametersource; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.expression.ExpressionException; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory.ParameterExpressionEvaluator; +import org.springframework.util.Assert; + +class ExpressionEvaluatingParameterSource implements PositionSupportingParameterSource { + + private static final Log logger = LogFactory.getLog(ExpressionEvaluatingParameterSource.class); + + private static final Object ERROR = new Object(); + + private final Object input; + + private volatile Map values = new HashMap(); + + private final Map parameterExpressions; + + private final List parameters; + + private final ParameterExpressionEvaluator expressionEvaluator; + + ExpressionEvaluatingParameterSource(Object input, List parameters, ParameterExpressionEvaluator expressionEvaluator) { + + this.input = input; + this.expressionEvaluator = expressionEvaluator; + this.parameters = parameters; + this.parameterExpressions = ExpressionEvaluatingParameterSourceFactory.convertExpressions(parameters); + this.values.putAll(ExpressionEvaluatingParameterSourceFactory.convertStaticParameters(parameters)); + + } + + public Object getValueByPosition(int position) { + + Assert.isTrue(position >= 0, "The position must be be non-negative."); + + if (position <= parameters.size()) { + + final JpaParameter parameter = parameters.get(position); + + if (parameter.getValue() != null) { + return parameter.getValue(); + } + + if (parameter.getExpression() != null) { + String expression = parameter.getExpression(); + + if (input instanceof Collection) { + expression = "#root.![" + expression + "]"; + } + + final Object value = this.expressionEvaluator.evaluateExpression(expression, input); + //FIXME values.put(paramName, value); + if (logger.isDebugEnabled()) { + logger.debug("Resolved expression " + expression + " to " + value); + } + return value; + + } + + } + + return null; + + } + + public Object getValue(String paramName) { + if (values.containsKey(paramName)) { + return values.get(paramName); + } + String expression = paramName; + if (parameterExpressions.containsKey(expression)) { + expression = parameterExpressions.get(expression); + } + if (input instanceof Collection) { + expression = "#root.![" + expression + "]"; + } + final Object value = this.expressionEvaluator.evaluateExpression(expression, input); + values.put(paramName, value); + if (logger.isDebugEnabled()) { + logger.debug("Resolved expression " + expression + " to " + value); + } + return value; + } + + public boolean hasValue(String paramName) { + try { + final Object value = getValue(paramName); + if (value == ERROR) { + return false; + } + } + catch (ExpressionException e) { + if (logger.isDebugEnabled()) { + logger.debug("Could not evaluate expression", e); + } + values.put(paramName, ERROR); + return false; + } + return true; + } +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactory.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactory.java new file mode 100644 index 0000000000..988789535d --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactory.java @@ -0,0 +1,133 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.integration.util.AbstractExpressionEvaluator; +import org.springframework.util.Assert; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class ExpressionEvaluatingParameterSourceFactory implements ParameterSourceFactory { + + private volatile List parameters; + private ParameterExpressionEvaluator expressionEvaluator = new ParameterExpressionEvaluator(); + + public ExpressionEvaluatingParameterSourceFactory() { + this.parameters = Collections.unmodifiableList(new ArrayList()); + } + + /** + * Define the (optional) parameter values. + * + * @param parameters the parameters to be set + */ + public void setParameters(List parameters) { + + Assert.notEmpty(parameters, "parameters must not be null or empty."); + + for (JpaParameter parameter : parameters) { + Assert.notNull(parameter, "The provided list (parameters) cannot contain null values."); + } + + this.parameters = parameters; + expressionEvaluator.getEvaluationContext().setVariable("staticParameters", convertStaticParameters(parameters)); + + } + + public PositionSupportingParameterSource createParameterSource(final Object input) { + return new ExpressionEvaluatingParameterSource(input, this.parameters, expressionEvaluator); + } + + /** + * Utility method that converts a Collection of {@link JpaParameter} to + * a Map containing only expression parameters. + * + * @param jpaParameters Must not be null. + * @return Map containing only the Expression bound parameters. Will never be null. + */ + public static Map convertExpressions(Collection jpaParameters) { + + Assert.notNull(jpaParameters, "The Collection of jpaParameters must not be null."); + + for (JpaParameter parameter : jpaParameters) { + Assert.notNull(parameter, "'jpaParameters' must not contain null values."); + } + + final Map staticParameters = new HashMap(); + + for (JpaParameter parameter : jpaParameters) { + if (parameter.getExpression() != null) { + staticParameters.put(parameter.getName(), parameter.getExpression()); + } + } + + return staticParameters; + } + + /** + * Utility method that converts a Collection of {@link JpaParameter} to + * a Map containing only static parameters. + * + * @param jpaParameters Must not be null. + * @return Map containing only the static parameters. Will never be null. + */ + public static Map convertStaticParameters(Collection jpaParameters) { + + Assert.notNull(jpaParameters, "The Collection of jpaParameters must not be null."); + + for (JpaParameter parameter : jpaParameters) { + Assert.notNull(parameter, "'jpaParameters' must not contain null values."); + } + + final Map staticParameters = new HashMap(); + + for (JpaParameter parameter : jpaParameters) { + if (parameter.getValue() != null) { + staticParameters.put(parameter.getName(), parameter.getValue()); + } + } + + return staticParameters; + } + + public class ParameterExpressionEvaluator extends AbstractExpressionEvaluator { + + @Override + public StandardEvaluationContext getEvaluationContext() { + return super.getEvaluationContext(); + } + + @Override + public Object evaluateExpression(String expression, Object input) { + return super.evaluateExpression(expression, input); + } + + } + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSource.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSource.java new file mode 100644 index 0000000000..a73f37381b --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSource.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public interface ParameterSource { + + /** + * Determine whether there is a value for the specified named parameter. + * @param paramName the name of the parameter + * @return whether there is a value defined + */ + boolean hasValue(String paramName); + + /** + * Return the parameter value for the requested named parameter. + * @param paramName the name of the parameter + * @return the value of the specified parameter + */ + Object getValue(String paramName); + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSourceFactory.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSourceFactory.java new file mode 100644 index 0000000000..a1360376fa --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/ParameterSourceFactory.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public interface ParameterSourceFactory { + + /** + * Return a new {@link ParameterSource}. + * @param input the raw message or query result to be transformed into a {@link ParameterSource} + */ + ParameterSource createParameterSource(Object input); + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/PositionSupportingParameterSource.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/PositionSupportingParameterSource.java new file mode 100644 index 0000000000..4eba71d6cf --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/PositionSupportingParameterSource.java @@ -0,0 +1,29 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public interface PositionSupportingParameterSource extends ParameterSource { + + Object getValueByPosition(int position); + +} diff --git a/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/package-info.java b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/package-info.java new file mode 100644 index 0000000000..332a290284 --- /dev/null +++ b/spring-integration-jpa/src/main/java/org/springframework/integration/jpa/support/parametersource/package-info.java @@ -0,0 +1,6 @@ +/** + * Provides generic support for ParameterSources and ParameterSource Factories. + * This classes are modeled after the equivalent classes in the JDBC Module. However, + * the provided classes here do not have any SQL or JPA specific dependencies. + */ +package org.springframework.integration.jpa.support.parametersource; diff --git a/spring-integration-jpa/src/main/resources/META-INF/spring.handlers b/spring-integration-jpa/src/main/resources/META-INF/spring.handlers new file mode 100644 index 0000000000..f075a1962a --- /dev/null +++ b/spring-integration-jpa/src/main/resources/META-INF/spring.handlers @@ -0,0 +1 @@ +http\://www.springframework.org/schema/integration/jpa=org.springframework.integration.jpa.config.xml.JpaNamespaceHandler \ No newline at end of file diff --git a/spring-integration-jpa/src/main/resources/META-INF/spring.schemas b/spring-integration-jpa/src/main/resources/META-INF/spring.schemas new file mode 100644 index 0000000000..143785f98e --- /dev/null +++ b/spring-integration-jpa/src/main/resources/META-INF/spring.schemas @@ -0,0 +1,2 @@ +http\://www.springframework.org/schema/integration/jpa/spring-integration-jpa-2.2.xsd=org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd +http\://www.springframework.org/schema/integration/jpa/spring-integration-jpa.xsd=org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd \ No newline at end of file diff --git a/spring-integration-jpa/src/main/resources/META-INF/spring.tooling b/spring-integration-jpa/src/main/resources/META-INF/spring.tooling new file mode 100644 index 0000000000..d03dba97b8 --- /dev/null +++ b/spring-integration-jpa/src/main/resources/META-INF/spring.tooling @@ -0,0 +1,4 @@ +# Tooling related information for the integration jpa namespace +http\://www.springframework.org/schema/integration/jpa@name=integration JPA Namespace +http\://www.springframework.org/schema/integration/jpa@prefix=int-jpa +http\://www.springframework.org/schema/integration/jpa@icon=org/springframework/integration/jdbc/config/xml/spring-integration-jpa.gif diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd new file mode 100644 index 0000000000..d2d42c6132 --- /dev/null +++ b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa-2.2.xsd @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + The definition for the Spring Integration JPA Inbound Channel Adapter. + + + + + + + + + + + + + + + + + + + + + + + + + specifies the parameter source that would be used to + provide additional parameters. + + + + + + + + + + + + + + + + Once entities have been retrieved from the database, shall + they be removed from the database? + + If instead of deleting the retrieved entities, you would + rather like to updated them, e.g. setting a flag in a + column marking the record as retrieved, please consider + using a subsequent Outbound Gateway (coupled with a payload enricher). + + + + + + + + + + If you want to automatically remove retrieved entities from + the database you can also specify using the 'delete-per-row' + attribute, whether the list of retrieved objects shall be + deleted on a 'per-object-basis (true) or whether the objects + shall be removed using a batch operation (false). The + attribute defaults to 'false'. + + + + + + + + + + + + + + + + + + + Defines an outbound Channel Adapter for updating a + database using the Java Persistence API (JPA). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reference to a ParameterSourceFactory. + + + + + + + + + + + + Channel from which messages will be output. + When a message is sent to this channel it will + cause the query + to be executed. + + + + + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to a SubscribableChannel. + + + + + + + + + + Defines the Spring Integration JPA Outbound Gateway + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The receiving Message Channel of this endpoint. + + + + + + + + + + + + Message Channel to which replies should be + sent, after receiving the database response. + + + + + + + + + + + + + + + + + + + + + + + The parameter source factory that would be used for evaluating the + parameters of the response JPA QL that would be evaluated + JPA outbound gateway + + + + + + + Specifies the order for invocation when this endpoint is connected as a + subscriber to a SubscribableChannel. + + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Identifies the underlying Spring bean definition, which is an + instance of either 'EventDrivenConsumer' or 'PollingConsumer', + depending on whether the component's input channel is a + 'SubscribableChannel' or 'PollableChannel'. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Flag to indicate that the component should start automatically + on startup (default true). + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa.gif b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa.gif new file mode 100644 index 0000000000..41b369fece Binary files /dev/null and b/spring-integration-jpa/src/main/resources/org/springframework/integration/jpa/config/xml/spring-integration-jpa.gif differ diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java new file mode 100644 index 0000000000..c22a5aac27 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.core.JpaOperations; +import org.springframework.integration.test.util.TestUtils; + +/** + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaInboundChannelAdapterParserTests { + + private ConfigurableApplicationContext context; + + private SourcePollingChannelAdapter consumer; + + @Test + public void testJpaInboundChannelAdapterParser() throws Exception { + + setUp("JpaInboundChannelAdapterParserTests.xml", getClass()); + + final AbstractMessageChannel outputChannel = TestUtils.getPropertyValue(this.consumer, "outputChannel", AbstractMessageChannel.class); + + assertEquals("out", outputChannel.getComponentName()); + + final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "source.jpaExecutor", JpaExecutor.class); + + assertNotNull(jpaExecutor); + + final Class 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); + + assertTrue(TestUtils.getPropertyValue(jpaExecutor, "expectSingleResult", Boolean.class)); + +} + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean("jpaInboundChannelAdapter", SourcePollingChannelAdapter.class); + } + +} 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 new file mode 100644 index 0000000000..20758e3854 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaInboundChannelAdapterParserTests.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java new file mode 100644 index 0000000000..c4e1b2be39 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.java @@ -0,0 +1,163 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Proxy; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.core.JpaOperations; +import org.springframework.integration.jpa.support.JpaParameter; +import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.test.util.TestUtils; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaMessageHandlerParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testJpaMessageHandlerParser() throws Exception { + setUp("JpaMessageHandlerParserTests.xml", getClass()); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + + assertEquals("target", inputChannel.getComponentName()); + + final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.jpaExecutor", JpaExecutor.class); + + assertNotNull(jpaExecutor); + + final String query = TestUtils.getPropertyValue(jpaExecutor, "jpaQuery", String.class); + + assertEquals("from Student", query); + + final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class); + + assertNotNull(jpaOperations); + + final PersistMode persistMode = TestUtils.getPropertyValue(jpaExecutor, "persistMode", PersistMode.class); + + assertEquals(PersistMode.PERSIST, persistMode); + + @SuppressWarnings("unchecked") + List jpaParameters = TestUtils.getPropertyValue(jpaExecutor, "jpaParameters", List.class); + + assertNotNull(jpaParameters); + assertTrue(jpaParameters.size() == 3); + + } + + @Test + public void testJpaMessageHandlerParserWithEntityManagerFactory() throws Exception { + setUp("JpaMessageHandlerParserTestsWithEmFactory.xml", getClass()); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + + assertEquals("target", inputChannel.getComponentName()); + + final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.jpaExecutor", JpaExecutor.class); + + assertNotNull(jpaExecutor); + + final String query = TestUtils.getPropertyValue(jpaExecutor, "jpaQuery", String.class); + + assertEquals("select student from Student student", query); + + final JpaOperations jpaOperations = TestUtils.getPropertyValue(jpaExecutor, "jpaOperations", JpaOperations.class); + + assertNotNull(jpaOperations); + + final PersistMode persistMode = TestUtils.getPropertyValue(jpaExecutor, "persistMode", PersistMode.class); + + assertEquals(PersistMode.PERSIST, persistMode); + + @SuppressWarnings("unchecked") + List jpaParameters = TestUtils.getPropertyValue(jpaExecutor, "jpaParameters", List.class); + + assertNotNull(jpaParameters); + assertTrue(jpaParameters.size() == 3); + +} + + @SuppressWarnings("unchecked") + @Test + public void testProcedurepParametersAreSet() throws Exception { + setUp("JpaMessageHandlerParserTestsWithEmFactory.xml", getClass()); + + final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.jpaExecutor", JpaExecutor.class); + final List jpaParameters = TestUtils.getPropertyValue(jpaExecutor, "jpaParameters", List.class); + + assertTrue(jpaParameters.size() == 3); + + JpaParameter parameter1 = jpaParameters.get(0); + JpaParameter parameter2 = jpaParameters.get(1); + JpaParameter parameter3 = jpaParameters.get(2); + + assertEquals("firstName", parameter1.getName()); + assertEquals("firstaName", parameter2.getName()); + assertEquals("updatedDateTime", parameter3.getName()); + + assertEquals("kenny", parameter1.getValue()); + assertEquals("cartman", parameter2.getValue()); + assertNull(parameter3.getValue()); + + assertNull(parameter1.getExpression()); + assertNull(parameter2.getExpression()); + assertEquals("new java.util.Date()", parameter3.getExpression()); + + } + + @Test + public void testTransactionalSettings() throws Exception { + + setUp("JpaMessageHandlerTransactionalParserTests.xml", getClass()); + + final Proxy proxy = TestUtils.getPropertyValue(this.consumer, "handler", Proxy.class); + assertNotNull(proxy); + + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean("jpaOutboundChannelAdapter", EventDrivenConsumer.class); + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml new file mode 100644 index 0000000000..116f0ba24e --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTests.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTestsWithEmFactory.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTestsWithEmFactory.xml new file mode 100644 index 0000000000..76e2138354 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerParserTestsWithEmFactory.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerTransactionalParserTests.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerTransactionalParserTests.xml new file mode 100644 index 0000000000..8fc75aa589 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaMessageHandlerTransactionalParserTests.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 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 new file mode 100644 index 0000000000..70a0401e4b --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.java @@ -0,0 +1,90 @@ +/* + * Copyright 2002-2012 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.jpa.config.xml; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.After; +import org.junit.Test; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.integration.channel.AbstractMessageChannel; +import org.springframework.integration.endpoint.EventDrivenConsumer; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.core.JpaOperations; +import org.springframework.integration.jpa.outbound.JpaOutboundGateway; +import org.springframework.integration.jpa.support.OutboundGatewayType; +import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.test.util.TestUtils; + +/** + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaOutboundGatewayParserTests { + + private ConfigurableApplicationContext context; + + private EventDrivenConsumer consumer; + + @Test + public void testJpaOutboundGatewayParser() throws Exception { + setUp("JpaOutboundGatewayParserTests.xml", getClass()); + + + final AbstractMessageChannel inputChannel = TestUtils.getPropertyValue(this.consumer, "inputChannel", AbstractMessageChannel.class); + + assertEquals("in", inputChannel.getComponentName()); + + final JpaOutboundGateway jpaOutboundGateway = TestUtils.getPropertyValue(this.consumer, "handler", JpaOutboundGateway.class); + + final OutboundGatewayType gatewayType = TestUtils.getPropertyValue(jpaOutboundGateway, "gatewayType", OutboundGatewayType.class); + + assertEquals(OutboundGatewayType.RETRIEVING, gatewayType); + + final JpaExecutor jpaExecutor = TestUtils.getPropertyValue(this.consumer, "handler.jpaExecutor", JpaExecutor.class); + + assertNotNull(jpaExecutor); + + final Class 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); + + final PersistMode persistMode = TestUtils.getPropertyValue(jpaExecutor, "persistMode", PersistMode.class); + + assertEquals(PersistMode.PERSIST, persistMode); + + assertTrue(TestUtils.getPropertyValue(jpaExecutor, "expectSingleResult", Boolean.class)); + + } + + @After + public void tearDown(){ + if(context != null){ + context.close(); + } + } + + public void setUp(String name, Class cls){ + context = new ClassPathXmlApplicationContext(name, cls); + consumer = this.context.getBean("jpaOutboundGateway", EventDrivenConsumer.class); + } + +} 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 new file mode 100644 index 0000000000..b0adfcad31 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/config/xml/JpaOutboundGatewayParserTests.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java new file mode 100644 index 0000000000..c962e5456f --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/AbstractJpaOperationsTests.java @@ -0,0 +1,343 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.EntityManager; + +import org.junit.Assert; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory; +import org.springframework.integration.jpa.support.parametersource.ParameterSource; +import org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory; +import org.springframework.integration.jpa.test.JpaTestUtils; +import org.springframework.integration.jpa.test.entity.Gender; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * @author Gunnar Hillert + * @since 2.2 + * + */ +@Transactional +public class AbstractJpaOperationsTests { + + @Autowired + protected PlatformTransactionManager transactionManager; + + @Autowired + protected EntityManager entityManager; + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#executeUpdate(java.lang.String, org.springframework.integration.jpa.core.JpaQLParameterSource)}. + */ + public void testGetAllStudents() { + + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List students = jpaOperations.getResultListForClass(StudentDomain.class, 0); + Assert.assertTrue(students.size() == 3); + + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#executeUpdate(java.lang.String, org.springframework.integration.jpa.core.JpaQLParameterSource)}. + */ + public void testGetAllStudentsWithMaxResults() { + + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List students = jpaOperations.getResultListForClass(StudentDomain.class, 2); + Assert.assertTrue(String.format("Was expecting 2 Students to be returned but got '%s'.", students.size()), + students.size() == 2); + + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#executeUpdate(java.lang.String, org.springframework.integration.jpa.core.JpaQLParameterSource)}. + */ + public void testExecuteUpdate() { + + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + List students = jpaOperations.getResultListForClass(StudentDomain.class, 0); + Assert.assertTrue(students.size() == 3); + + ParameterSourceFactory requestParameterSourceFactory = new ExpressionEvaluatingParameterSourceFactory(); + ParameterSource source = requestParameterSourceFactory.createParameterSource(student); + + int updatedRecords = jpaOperations.executeUpdate("update Student s set s.lastName = :lastName, s.lastUpdated = :lastUpdated " + + "where s.rollNumber in (select max(a.rollNumber) from Student a)", source); + + entityManager.flush(); + + Assert.assertTrue( 1 == updatedRecords); + Assert.assertNull(student.getRollNumber()); + + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#executeUpdateWithNamedQuery(java.lang.String, org.springframework.integration.jpa.core.JpaQLParameterSource)}. + */ + public void testExecuteUpdateWithNamedQuery() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + ParameterSourceFactory requestParameterSourceFactory = new ExpressionEvaluatingParameterSourceFactory(); + ParameterSource source = requestParameterSourceFactory.createParameterSource(student); + + int updatedRecords = jpaOperations.executeUpdateWithNamedQuery("updateStudent", source); + + entityManager.flush(); + + Assert.assertTrue( 1 == updatedRecords); + Assert.assertNull(student.getRollNumber()); + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#executeUpdateWithNativeQuery(java.lang.String, org.springframework.integration.jpa.core.JpaQLParameterSource)}. + */ + public void testExecuteUpdateWithNativeQuery() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + ParameterSourceFactory requestParameterSourceFactory = new ExpressionEvaluatingParameterSourceFactory(); + ParameterSource source = requestParameterSourceFactory.createParameterSource(student); + + int updatedRecords = jpaOperations.executeUpdateWithNativeQuery("update Student set lastName = :lastName, lastUpdated = :lastUpdated " + + "where rollNumber in (select max(a.rollNumber) from Student a)", source); + + entityManager.flush(); + + Assert.assertTrue( 1 == updatedRecords); + Assert.assertNull(student.getRollNumber()); + } + + /** + * Test method for {@link DefaultJpaOperations#getResultListForNativeQuery(String, Class, JpaQLParameterSource, int, int)}. + * @throws ParseException + */ + public void testExecuteSelectWithNativeQueryReturningEntityClass() throws ParseException { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + String selectSqlQuery = "select * from Student where lastName = 'Last One'"; + + Class entityClass = StudentDomain.class; + + List students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, entityClass, null, 0); + + Assert.assertTrue(students.size() == 1); + + StudentDomain retrievedStudent = (StudentDomain) students.iterator().next(); + + SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); + + assertEquals(formatter.parse("1980/01/01"), retrievedStudent.getDateOfBirth()); + assertEquals("First One", retrievedStudent.getFirstName()); + assertEquals(Gender.MALE, retrievedStudent.getGender()); + assertEquals("Last One", retrievedStudent.getLastName()); + assertNotNull(retrievedStudent.getLastUpdated()); + + } + + /** + * Test method for {@link DefaultJpaOperations#getResultListForNativeQuery(String, Class, JpaQLParameterSource, int, int)}. + * @throws ParseException + */ + public void testExecuteSelectWithNativeQuery() throws ParseException { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + String selectSqlQuery = "select rollNumber, firstName, lastName, gender, dateOfBirth, lastUpdated from Student where lastName = 'Last One'"; + + List students = jpaOperations.getResultListForNativeQuery(selectSqlQuery, null, null, 0); + + Assert.assertTrue(students.size() == 1); + + Object[] retrievedStudent = (Object[]) students.iterator().next(); + + SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd"); + + assertNotNull(retrievedStudent[0]); + assertEquals("First One", retrievedStudent[1]); + assertEquals("Last One", retrievedStudent[2]); + assertEquals("M", retrievedStudent[3]); + assertEquals(formatter.parse("1980/01/01"), retrievedStudent[4]); + assertNotNull(retrievedStudent[5]); + + } + + public void testExecuteUpdateWithNativeNamedQuery() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + ParameterSourceFactory requestParameterSourceFactory = new ExpressionEvaluatingParameterSourceFactory(); + ParameterSource source = requestParameterSourceFactory.createParameterSource(student); + + int updatedRecords = jpaOperations.executeUpdateWithNamedQuery("updateStudentNativeQuery", source); + + entityManager.flush(); + + Assert.assertTrue( 1 == updatedRecords); + Assert.assertNull(student.getRollNumber()); + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#merge(java.lang.Object)}. + */ + public void testMerge() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + Assert.assertNull(student.getRollNumber()); + final StudentDomain savedStudent = (StudentDomain) jpaOperations.merge(student); + entityManager.flush(); + Assert.assertNull(student.getRollNumber()); + Assert.assertNotNull(savedStudent); + Assert.assertNotNull(savedStudent.getRollNumber()); + + Assert.assertTrue(student != savedStudent); + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#persist(java.lang.Object)}. + */ + public void testPersist() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final StudentDomain student = JpaTestUtils.getTestStudent(); + + Assert.assertNull(student.getRollNumber()); + jpaOperations.persist(student); + entityManager.flush(); + Assert.assertNotNull(student.getRollNumber()); + } + + + public void testDeleteInBatch() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List students = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(students); + + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeOtherTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + + jpaOperations.deleteInBatch(students); + + entityManager.flush(); + + transactionManager.commit(status); + + final List studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(studentsFromDb); + Assert.assertTrue(studentsFromDb.size() == 0); + + } + + protected JpaOperations getJpaOperations(EntityManager entityManager) { + + final DefaultJpaOperations jpaOperationsImpl = new DefaultJpaOperations(); + jpaOperationsImpl.setEntityManager(entityManager); + jpaOperationsImpl.afterPropertiesSet(); + + return jpaOperationsImpl; + } + + public void testDelete() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(studentsFromDb); + Assert.assertTrue(studentsFromDb.size() == 3); + + + final StudentDomain student = jpaOperations.find(StudentDomain.class, 1001L); + + Assert.assertNotNull(student); + + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeOtherTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + + jpaOperations.delete(student); + + entityManager.flush(); + + transactionManager.commit(status); + + final List studentsFromDbAfterDelete = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(studentsFromDbAfterDelete); + Assert.assertTrue(studentsFromDbAfterDelete.size() == 2); + + } + + public void testDeleteInBatchWithEmptyCollection() { + final JpaOperations jpaOperations = getJpaOperations(entityManager); + + final List students = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(students); + Assert.assertTrue(students.size() == 3); + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeOtherTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + + jpaOperations.deleteInBatch(new ArrayList(0)); + + entityManager.flush(); + + transactionManager.commit(status); + + final List studentsFromDb = jpaOperations.getResultListForClass(StudentDomain.class, 0); + + Assert.assertNotNull(studentsFromDb); + Assert.assertTrue(studentsFromDb.size() == 3); //Nothing should have happened + + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml new file mode 100644 index 0000000000..c0aac87c6d --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests-context.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java new file mode 100644 index 0000000000..f986c561f0 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/EclipseLinkJpaOperationsTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.text.ParseException; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Tests the functionality of {@link JpaOperations} and {@link DefaultJpaOperations} + * using the EclipseLink persistence provider. + * + * If you want to run these tests from your IDE, please ensure that you execute + * the tests using a javaagent: + * + *
+ * {@code
+ * -javaagent:/home//.m2/repository/org/springframework/spring-instrument/3.1.1.RELEASE/spring-instrument-3.1.1.RELEASE.jar
+ * }
+ * 
+ * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class EclipseLinkJpaOperationsTests extends AbstractJpaOperationsTests { + + @Test + @Override + public void testExecuteUpdateWithNativeQuery() { + + try { + super.testExecuteUpdateWithNativeQuery(); + } catch (Exception e) { + return; + } + + Assert.fail("Was expecting an Exception as OpenJPA does not support Native SQL Queries with Named Parameters."); + } + + @Test + @Override + public void testExecuteUpdateWithNativeNamedQuery() { + + try { + super.testExecuteUpdateWithNativeNamedQuery(); + } catch (Exception e) { + return; + } + + Assert.fail("Was expecting an Exception as OpenJPA does not support Native SQL Queries with Named Parameters."); + } + + @Test + @Override + public void testExecuteUpdate() { + super.testExecuteUpdate(); + } + + @Test + @Override + public void testExecuteUpdateWithNamedQuery() { + super.testExecuteUpdateWithNamedQuery(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQueryReturningEntityClass() + throws ParseException { + super.testExecuteSelectWithNativeQueryReturningEntityClass(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQuery() throws ParseException { + super.testExecuteSelectWithNativeQuery(); + } + + @Test + @Override + public void testMerge() { + super.testMerge(); + } + + @Test + @Override + public void testPersist() { + super.testPersist(); + } + + @Test + @Override + public void testGetAllStudents() { + super.testGetAllStudents(); + } + + @Test + @Override + public void testGetAllStudentsWithMaxResults() { + super.testGetAllStudentsWithMaxResults(); + } + + @Test + @Override + public void testDeleteInBatch() { + super.testDeleteInBatch(); + } + + @Test + @Override + public void testDelete() { + super.testDelete(); + } + + @Test + @Override + public void testDeleteInBatchWithEmptyCollection() { + super.testDeleteInBatchWithEmptyCollection(); + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests-context.xml new file mode 100644 index 0000000000..c27342a498 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests-context.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java new file mode 100644 index 0000000000..b0db35e35c --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/HibernateJpaOperationsTests.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.sql.SQLException; +import java.text.ParseException; +import java.util.Map; + +import javax.sql.DataSource; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Configuration; +import org.hibernate.ejb.Ejb3Configuration; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class HibernateJpaOperationsTests extends AbstractJpaOperationsTests { + + @Autowired + private DataSource dataSource; + + @Autowired + private LocalContainerEntityManagerFactoryBean fb; + + /** + * Little helper that allows you to generate the DDL via Hibernate. The + * DDL is printed to the console. + */ + @Test + public void generateDdl() { + + final Ejb3Configuration cfg = new Ejb3Configuration(); + + Map properties = fb.getJpaPropertyMap(); + + + properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + + final Ejb3Configuration configured = cfg.configure( fb.getPersistenceUnitInfo(), fb.getJpaPropertyMap() ); + final Configuration configuration = configured.getHibernateConfiguration(); + + final SchemaExport schemaExport; + + try { + schemaExport = new SchemaExport(configuration, dataSource.getConnection()); + } catch (HibernateException e) { + throw new IllegalStateException(e); + } catch (SQLException e) { + throw new IllegalStateException(e); + } + + schemaExport.create(true, false); + + } + + @Test + @Override + public void testExecuteUpdate() { + super.testExecuteUpdate(); + } + + @Test + @Override + public void testGetAllStudents() { + super.testGetAllStudents(); + } + + @Test + @Override + public void testGetAllStudentsWithMaxResults() { + super.testGetAllStudentsWithMaxResults(); + } + + @Test + @Override + public void testExecuteUpdateWithNamedQuery() { + super.testExecuteUpdateWithNamedQuery(); + } + + @Test + @Override + public void testExecuteUpdateWithNativeQuery() { + super.testExecuteUpdateWithNativeQuery(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQueryReturningEntityClass() + throws ParseException { + super.testExecuteSelectWithNativeQueryReturningEntityClass(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQuery() throws ParseException { + super.testExecuteSelectWithNativeQuery(); + } + + @Test + @Override + public void testExecuteUpdateWithNativeNamedQuery() { + super.testExecuteUpdateWithNativeNamedQuery(); + } + + @Test + @Override + public void testMerge() { + super.testMerge(); + } + + @Test + @Override + public void testPersist() { + super.testPersist(); + } + + @Test + @Override + public void testDeleteInBatch() { + super.testDeleteInBatch(); + } + + @Test + @Override + public void testDelete() { + super.testDelete(); + } + + @Test + @Override + public void testDeleteInBatchWithEmptyCollection() { + super.testDeleteInBatchWithEmptyCollection(); + } + +} 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 new file mode 100644 index 0000000000..82ff68f051 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/JpaExecutorTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import static org.mockito.Mockito.mock; + +import javax.persistence.EntityManager; + +import junit.framework.Assert; + +import org.junit.Test; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaExecutorTests { + + /** + * In this test, the {@link JpaExecutor}'s poll method will be called without + * specifying a 'query', 'namedQuery' or 'entityClass' property. This should + * result in an {@link IllegalArgumentException}. + * + * @throws Exception + */ + @Test + public void testExecutePollWithNoEntityClassSpecified() throws Exception { + + final JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class)); + + try { + jpaExecutor.poll(); + } catch (IllegalStateException e) { + Assert.assertEquals("Exception Message does not match.", + "For the polling operation, one of " + + "the following properties must be specified: " + + "query, namedQuery or entityClass.", e.getMessage()); + return; + } + + Assert.fail("Was expecting an IllegalStateException to be thrown."); + + } + + /** + */ + @Test + public void testInstatiateJpaExecutorWithNullJpaOperations() { + + JpaOperations jpaOperations = null; + + try { + new JpaExecutor(jpaOperations); + } catch (IllegalArgumentException e) { + Assert.assertEquals("jpaOperations must not be null.", e.getMessage()); + } + + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml new file mode 100644 index 0000000000..517385052e --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests-context.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java new file mode 100644 index 0000000000..067948da6d --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/core/OpenJpaJpaOperationsTests.java @@ -0,0 +1,182 @@ +/* + * Copyright 2002-2012 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.jpa.core; + +import java.io.IOException; +import java.sql.SQLException; +import java.text.ParseException; + +import junit.framework.Assert; + +import org.apache.openjpa.jdbc.conf.JDBCConfiguration; +import org.apache.openjpa.jdbc.conf.JDBCConfigurationImpl; +import org.apache.openjpa.jdbc.meta.MappingTool; +import org.apache.openjpa.lib.conf.Configurations; +import org.apache.openjpa.lib.util.Options; +import org.apache.openjpa.persistence.InvalidStateException; +import org.apache.openjpa.persistence.PersistenceException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * Tests the functionality of {@link JpaOperations} and {@link DefaultJpaOperations} + * using the OpenJPA persistence provider. + * + * If you want to run these tests from your IDE, please ensure that you execute + * the tests using a javaagent: + * + *
+ * {@code
+ * -javaagent://openjpa-2.1.1.jar
+ * }
+ * 
+ * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class OpenJpaJpaOperationsTests extends AbstractJpaOperationsTests { + + @Test + @Override + public void testExecuteUpdateWithNativeQuery() { + + try { + super.testExecuteUpdateWithNativeQuery(); + } catch (PersistenceException e) { + return; + } + + Assert.fail("Was expecting an Exception as OpenJPA does not support Native SQL Queries with Named Parameters."); + } + + @Test + @Override + public void testExecuteUpdateWithNativeNamedQuery() { + + try { + super.testExecuteUpdateWithNativeNamedQuery(); + } catch (InvalidStateException e) { + return; + } + + Assert.fail("Was expecting an Exception as OpenJPA does not support Native SQL Queries with Named Parameters."); + } + + /** + * Test method for {@link org.springframework.integration.jpa.core.DefaultJpaOperations#persist(java.lang.Object)}. + * + * http://openjpa.apache.org/builds/1.0.4/apache-openjpa-1.0.4/docs/manual/manual.html#ref_guide_ddl_examples + */ + //@Test + public void testGenerateSchema() { + + String[] arguments = {}; + + Options opts = new Options(); + opts.put("schemaAction", "build"); + opts.put("sql", "build/database/openjpa-h2.sql"); + opts.put("org.springframework.integration.jpa.test.entity.Student", "true"); + + final String[] args = opts.setFromCmdLine(arguments); + + boolean ret = Configurations.runAgainstAllAnchors(opts, + new Configurations.Runnable() { + public boolean run(Options opts) throws IOException, SQLException { + JDBCConfiguration conf = new JDBCConfigurationImpl(); + conf.setConnectionDriverName("org.h2.Driver"); + conf.setConnectionURL("jdbc:h2:~/test"); + conf.setConnectionUserName("sa"); + conf.setConnectionPassword(""); + + try { + return MappingTool.run(conf, args, opts); + } finally { + conf.close(); + } + } + }); + + } + + @Test + @Override + public void testExecuteUpdate() { + super.testExecuteUpdate(); + } + + @Test + @Override + public void testExecuteUpdateWithNamedQuery() { + super.testExecuteUpdateWithNamedQuery(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQueryReturningEntityClass() + throws ParseException { + super.testExecuteSelectWithNativeQueryReturningEntityClass(); + } + + @Test + @Override + public void testExecuteSelectWithNativeQuery() throws ParseException { + super.testExecuteSelectWithNativeQuery(); + } + + @Test + @Override + public void testMerge() { + super.testMerge(); + } + + @Test + @Override + public void testPersist() { + super.testPersist(); + } + + @Test + @Override + public void testGetAllStudents() { + super.testGetAllStudents(); + } + + @Test + @Override + public void testGetAllStudentsWithMaxResults() { + super.testGetAllStudentsWithMaxResults(); + } + + @Test + @Override + public void testDeleteInBatch() { + super.testDeleteInBatch(); + } + + @Test + @Override + public void testDelete() { + super.testDelete(); + } + + @Test + @Override + public void testDeleteInBatchWithEmptyCollection() { + super.testDeleteInBatchWithEmptyCollection(); + } +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/BaseJpaPollingChannelAdapterTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/BaseJpaPollingChannelAdapterTests-context.xml new file mode 100644 index 0000000000..b7ffe26af3 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/BaseJpaPollingChannelAdapterTests-context.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests-context.xml new file mode 100644 index 0000000000..0f10e0d268 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ 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 new file mode 100644 index 0000000000..f43ef6b6ef --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterTests.java @@ -0,0 +1,477 @@ +/* + * Copyright 2002-2012 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.jpa.inbound; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +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.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.core.JpaOperations; +import org.springframework.integration.jpa.test.Consumer; +import org.springframework.integration.jpa.test.JpaTestUtils; +import org.springframework.integration.jpa.test.TestTrigger; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.integration.scheduling.PollerMetadata; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the Jpa Polling Channel Adapter {@link JpaPollingChannelAdapter}. + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true) +public class JpaPollingChannelAdapterTests { + + @Autowired + private GenericApplicationContext context; + + @Autowired + EntityManager entityManager; + + @Autowired + JpaOperations jpaOperations; + + @Autowired + @Qualifier("jpaPoller") + PollerMetadata poller; + + @Autowired + @Qualifier("outputChannel") + private MessageChannel outputChannel; + + @Autowired + TestTrigger testTrigger; + + /** + * In this test, a Jpa Polling Channel Adapter will use a plain entity class + * to retrieve a list of records from the database. + * + * @throws Exception + */ + @Test + @DirtiesContext + public void testWithEntityClass() throws Exception { + testTrigger.reset(); + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setEntityClass(StudentDomain.class); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection primeNumbers = message.getPayload(); + + assertTrue(primeNumbers.size() == 3); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use JpQL query + * to retrieve a list of records from the database. + * + * @throws Exception + */ + @Test + public void testWithJpaQuery() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setJpaQuery("from Student"); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection primeNumbers = message.getPayload(); + + assertTrue(primeNumbers.size() == 3); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use JpQL query + * to retrieve a list of records from the database with a maxRows value of 1. + * + * @throws Exception + */ + @Test + public void testWithJpaQueryAndMaxResults() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setJpaQuery("from Student"); + jpaExecutor.setMaxRows(1); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection primeNumbers = message.getPayload(); + + assertTrue(primeNumbers.size() == 1); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use JpQL query + * to retrieve a list of records from the database. + * + * @throws Exception + */ + @Test + public void testWithJpaQueryOneResultOnly() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setJpaQuery("from Student s where s.firstName = 'First Two'"); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection students = message.getPayload(); + + assertTrue(students.size() == 1); + + StudentDomain student = (StudentDomain) students.iterator().next(); + + assertEquals("Last Two", student.getLastName()); + } + + /** + * In this test, a Jpa Polling Channel Adapter will use JpQL query + * to retrieve a list of records from the database. Additionaly, the records + * will be deleted after the polling. + * + * @throws Exception + */ + @Test + @DirtiesContext + @Transactional + public void testWithJpaQueryAndDelete() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setJpaQuery("from Student s"); + jpaExecutor.setDeleteAfterPoll(true); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull("Message is null.", message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection students = message.getPayload(); + + assertTrue(students.size() == 3); + + Long studentCount = entityManager.createQuery("select count(*) from Student", Long.class).getSingleResult(); + + assertEquals(Long.valueOf(0), studentCount); + + } + + @Test + @DirtiesContext + @Transactional + public void testWithJpaQueryButNoResultsAndDelete() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setJpaQuery("from Student s where s.lastName = 'Something Else'"); + jpaExecutor.setDeleteAfterPoll(true); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNull(message); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use JpQL query + * to retrieve a list of records from the database. Additionaly, the records + * will be deleted after the polling. + * + * @throws Exception + */ + @Test + @DirtiesContext + public void testWithJpaQueryAndDeletePerRow() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + final JpaExecutor jpaExecutor = new JpaExecutor(jpaOperations); + jpaExecutor.setJpaQuery("from Student s"); + jpaExecutor.setDeleteAfterPoll(true); + jpaExecutor.setDeletePerRow(true); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + //adapter.stop(); + + assertNotNull("Message is null.", message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection students = message.getPayload(); + + assertTrue(students.size() == 3); + + Long studentCount = entityManager.createQuery("select count(*) from Student", Long.class).getSingleResult(); + + assertEquals(Long.valueOf(0), studentCount); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use a Native SQL query + * to retrieve a list of records from the database. + * + * @throws Exception + */ + @Test + public void testWithNativeSqlQuery() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setNativeQuery("select * from Student where lastName = 'Last One'"); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection students = message.getPayload(); + + assertTrue(students.size() == 1); + + } + + /** + * In this test, a Jpa Polling Channel Adapter will use Named query + * to retrieve a list of records from the database. + * + * @throws Exception + */ + @Test + public void testWithNamedQuery() throws Exception { + testTrigger.reset(); + + //~~~~SETUP~~~~~ + + final JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setNamedQuery("selectStudent"); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + + final SourcePollingChannelAdapter adapter = JpaTestUtils.getSourcePollingChannelAdapter( + jpaPollingChannelAdapter, this.outputChannel, this.poller, this.context, this.getClass().getClassLoader()); + adapter.start(); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + final List>> received = new ArrayList>>(); + + final Consumer consumer = new Consumer(); + + received.add(consumer.poll(5000)); + + Message> message = received.get(0); + + adapter.stop(); + + assertNotNull(message); + assertNotNull(message.getPayload()); + assertNotNull(message.getPayload() instanceof Collection); + + Collection students = message.getPayload(); + + assertTrue(students.size() == 1); + + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterUnitTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterUnitTests.java new file mode 100644 index 0000000000..7dab1a70d4 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/inbound/JpaPollingChannelAdapterUnitTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2002-2012 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.jpa.inbound; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.springframework.integration.jpa.core.JpaExecutor; + +/** + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class JpaPollingChannelAdapterUnitTests { + + /** + * + */ + @Test + public void testReceiveNull() { + + JpaExecutor jpaExecutor = mock(JpaExecutor.class); + + when(jpaExecutor.poll()).thenReturn(null); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + assertNull(jpaPollingChannelAdapter.receive()); + + } + + /** + * + */ + @Test + public void testReceiveNotNull() { + + JpaExecutor jpaExecutor = mock(JpaExecutor.class); + + when(jpaExecutor.poll()).thenReturn("Spring"); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + assertNotNull("Expecting a Message to be returned.", jpaPollingChannelAdapter.receive()); + assertEquals("Spring", jpaPollingChannelAdapter.receive().getPayload()); + + } + + /** + * + */ + @Test + public void testGetComponentType() { + + JpaExecutor jpaExecutor = mock(JpaExecutor.class); + + final JpaPollingChannelAdapter jpaPollingChannelAdapter = new JpaPollingChannelAdapter(jpaExecutor); + assertEquals("jpa:inbound-channel-adapter", jpaPollingChannelAdapter.getComponentType()); + + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/BaseJpaPollingChannelAdapterTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/BaseJpaPollingChannelAdapterTests-context.xml new file mode 100644 index 0000000000..89ca5ad91c --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/BaseJpaPollingChannelAdapterTests-context.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests-context.xml new file mode 100644 index 0000000000..25415b208d --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests-context.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests.java new file mode 100644 index 0000000000..a296d00353 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTests.java @@ -0,0 +1,168 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import java.util.List; + +import javax.persistence.EntityManager; +import javax.sql.DataSource; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.Message; +import org.springframework.integration.jpa.core.JpaExecutor; +import org.springframework.integration.jpa.support.PersistMode; +import org.springframework.integration.jpa.test.JpaTestUtils; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true) +public class JpaOutboundChannelAdapterTests { + + @Autowired + private EntityManager entityManager; + + @Autowired + DataSource dataSource; + + @Autowired + private PlatformTransactionManager transactionManager; + + @Test + @DirtiesContext + public void saveEntityWithMerge() throws InterruptedException { + + List results1 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results1); + Assert.assertTrue(results1.size() == 3); + + JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setEntityClass(StudentDomain.class); + jpaExecutor.afterPropertiesSet(); + + JpaOutboundGateway jpaOutboundChannelAdapter = new JpaOutboundGateway(jpaExecutor); + jpaOutboundChannelAdapter.setProducesReply(false); + + StudentDomain testStudent = JpaTestUtils.getTestStudent(); + Message message = MessageBuilder.withPayload(testStudent).build(); + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + jpaOutboundChannelAdapter.handleMessage(message); + transactionManager.commit(status); + + List results2 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results2); + Assert.assertTrue(results2.size() == 4); + + Assert.assertNull(testStudent.getRollNumber()); + + } + + @Test + @DirtiesContext + public void saveEntityWithMergeWithoutSpecifyingEntityClass() throws InterruptedException { + + List results1 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results1); + Assert.assertTrue(results1.size() == 3); + + JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.afterPropertiesSet(); + + JpaOutboundGateway jpaOutboundChannelAdapter = new JpaOutboundGateway(jpaExecutor); + jpaOutboundChannelAdapter.setProducesReply(false); + + StudentDomain testStudent = JpaTestUtils.getTestStudent(); + Message message = MessageBuilder.withPayload(testStudent).build(); + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + jpaOutboundChannelAdapter.handleMessage(message); + transactionManager.commit(status); + + List results2 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results2); + Assert.assertTrue(results2.size() == 4); + + Assert.assertNull(testStudent.getRollNumber()); + + } + + @Test + public void saveEntityWithPersist() throws InterruptedException { + + List results1 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results1); + Assert.assertTrue(results1.size() == 3); + + JpaExecutor jpaExecutor = new JpaExecutor(entityManager); + jpaExecutor.setEntityClass(StudentDomain.class); + jpaExecutor.setPersistMode(PersistMode.PERSIST); + jpaExecutor.afterPropertiesSet(); + + JpaOutboundGateway jpaOutboundChannelAdapter = new JpaOutboundGateway(jpaExecutor); + jpaOutboundChannelAdapter.setProducesReply(false); + + StudentDomain testStudent = JpaTestUtils.getTestStudent(); + + Assert.assertNull(testStudent.getRollNumber()); + + Message message = MessageBuilder.withPayload(testStudent).build(); + + jpaOutboundChannelAdapter.afterPropertiesSet(); + + DefaultTransactionDefinition def = new DefaultTransactionDefinition(); + // explicitly setting the transaction name is something that can only be done programmatically + def.setName("SomeTxName"); + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); + + TransactionStatus status = transactionManager.getTransaction(def); + jpaOutboundChannelAdapter.handleMessage(message); + transactionManager.commit(status); + + List results2 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results2); + Assert.assertTrue(results2.size() == 4); + + Assert.assertNotNull(testStudent.getRollNumber()); + } +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests-context.xml b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests-context.xml new file mode 100644 index 0000000000..9cf9400387 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests-context.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests.java new file mode 100644 index 0000000000..e111862c06 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundChannelAdapterTransactionalTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import java.util.List; + +import javax.sql.DataSource; + +import junit.framework.Assert; + +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.integration.Message; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.jpa.test.JpaTestUtils; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.integration.support.MessageBuilder; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +public class JpaOutboundChannelAdapterTransactionalTests { + + @Autowired + @Qualifier("input") + private MessageChannel channel; + + @Autowired + DataSource dataSource; + + @Test + @DirtiesContext + public void saveEntityWithTransaction() throws InterruptedException { + + List results1 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results1); + Assert.assertTrue(results1.size() == 3); + + StudentDomain testStudent = JpaTestUtils.getTestStudent(); + Message message = MessageBuilder.withPayload(testStudent).build(); + + channel.send(message); + + List results2 = new JdbcTemplate(dataSource).queryForList("Select * from Student"); + Assert.assertNotNull(results2); + Assert.assertTrue(results2.size() == 4); + + Assert.assertNull(testStudent.getRollNumber()); + + } + +} 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 new file mode 100644 index 0000000000..ecee0f645f --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests-context.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java new file mode 100644 index 0000000000..3b661bffa7 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/JpaOutboundGatewayTests.java @@ -0,0 +1,132 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import java.util.List; + +import junit.framework.Assert; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.integration.MessageHandlingException; +import org.springframework.integration.jpa.test.JpaTestUtils; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.transaction.TransactionConfiguration; +import org.springframework.transaction.annotation.Transactional; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true) +public class JpaOutboundGatewayTests { + + @Autowired + private StudentService studentService; + + @Test + @DirtiesContext + public void getStudent() { + final StudentDomain student = studentService.getStudent(1001L); + Assert.assertNotNull(student); + } + + @Test + @DirtiesContext + @Transactional + public void deleteNonExistingStudent() { + + StudentDomain student = JpaTestUtils.getTestStudent(); + student.setRollNumber(3424234234L); + + try { + studentService.deleteStudent(student); + } catch (IllegalArgumentException e) { + return; + } + + Assert.fail("Was expecting a MessageHandlingException to be thrown."); + } + + @Test + @DirtiesContext + public void getStudentWithException() { + try { + studentService.getStudentWithException(1001L); + } catch (MessageHandlingException e) { + Assert.assertEquals("The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.", + e.getMessage()); + + return; + } + + Assert.fail("Was expecting a MessageHandlingException to be thrown."); + } + + @Test + @DirtiesContext + public void getStudentStudentWithPositionalParameters() { + + StudentDomain student = studentService.getStudentWithParameters("First Two"); + + Assert.assertEquals("First Two", student.getFirstName()); + Assert.assertEquals("Last Two", student.getLastName()); + } + + @Test + @DirtiesContext + public void getAllStudents() { + + final List students = studentService.getAllStudents(); + Assert.assertNotNull(students); + Assert.assertTrue(students.size() == 3); + + } + + @Test + @DirtiesContext + @Transactional + public void persistStudent() { + + final StudentDomain studentToPersist = JpaTestUtils.getTestStudent(); + Assert.assertNull(studentToPersist.getRollNumber()); + + final StudentDomain persistedStudent = studentService.persistStudent(studentToPersist); + Assert.assertNotNull(persistedStudent); + Assert.assertNotNull(persistedStudent.getRollNumber()); + + } + + @Test + @DirtiesContext + @Transactional + public void persistStudentUsingMerge() { + + final StudentDomain studentToPersist = JpaTestUtils.getTestStudent(); + Assert.assertNull(studentToPersist.getRollNumber()); + + final StudentDomain persistedStudent = studentService.persistStudentUsingMerge(studentToPersist); + Assert.assertNotNull(persistedStudent); + Assert.assertNotNull(persistedStudent.getRollNumber()); + + } + +} 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 new file mode 100644 index 0000000000..c51fb08d51 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/outbound/StudentService.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2012 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.jpa.outbound; + +import java.util.List; + +import org.springframework.integration.annotation.Payload; +import org.springframework.integration.jpa.test.entity.StudentDomain; + +public interface StudentService { + + StudentDomain getStudent(StudentDomain student); + + StudentDomain getStudentWithException(Long id); + + StudentDomain getStudent(Long id); + StudentDomain deleteStudent(StudentDomain student); + + @Payload("new java.util.Date()") + List getAllStudents(); + + StudentDomain persistStudent(StudentDomain student); + + StudentDomain persistStudentUsingMerge(StudentDomain studentToPersist); + + StudentDomain getStudentWithParameters(String firstName); + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactoryTests.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactoryTests.java new file mode 100644 index 0000000000..ddbde980dd --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/support/parametersource/ExpressionEvaluatingParameterSourceFactoryTests.java @@ -0,0 +1,154 @@ +/* + * Copyright 2002-2012 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.jpa.support.parametersource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.springframework.integration.jpa.support.JpaParameter; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class ExpressionEvaluatingParameterSourceFactoryTests { + + private ExpressionEvaluatingParameterSourceFactory factory = new ExpressionEvaluatingParameterSourceFactory(); + + @Test + public void testSetStaticParameters() { + factory.setParameters(Collections.singletonList(new JpaParameter("foo", "bar", null))); + ParameterSource source = factory.createParameterSource(null); + assertTrue(source.hasValue("foo")); + assertEquals("bar", source.getValue("foo")); + } + + @Test + public void testMapInput() { + ParameterSource source = factory.createParameterSource(Collections.singletonMap("foo", "bar")); + assertTrue(source.hasValue("foo")); + assertEquals("bar", source.getValue("foo")); + } + + @Test + public void testListOfMapsInput() { + @SuppressWarnings("unchecked") + ParameterSource source = factory.createParameterSource(Arrays.asList(Collections.singletonMap("foo", "bar"), + Collections.singletonMap("foo", "bucket"))); + String expression = "foo"; + assertTrue(source.hasValue(expression)); + assertEquals("[bar, bucket]", source.getValue(expression).toString()); + } + + @Test + public void testMapInputWithExpression() { + ParameterSource source = factory.createParameterSource(Collections.singletonMap("foo", "bar")); + // This is an illegal parameter name in Spring JDBC so we'd never get this as input + assertTrue(source.hasValue("foo.toUpperCase()")); + assertEquals("BAR", source.getValue("foo.toUpperCase()")); + } + + @Test + public void testMapInputWithMappedExpression() { + factory.setParameters(Collections.singletonList(new JpaParameter("spam", null, "foo.toUpperCase()"))); + ParameterSource source = factory.createParameterSource(Collections.singletonMap("foo", "bar")); + assertTrue(source.hasValue("spam")); + assertEquals("BAR", source.getValue("spam")); + } + + @Test + public void testMapInputWithMappedExpressionResolveStatic() { + + List parameters = new ArrayList(); + parameters.add(new JpaParameter("spam", null, "#staticParameters['foo'].toUpperCase()")); + parameters.add(new JpaParameter("foo", "bar", null)); + factory.setParameters(parameters); + + ParameterSource source = factory.createParameterSource(Collections.singletonMap("crap", "bucket")); + assertTrue(source.hasValue("spam")); + assertEquals("BAR", source.getValue("spam")); + } + + @Test + public void testListOfMapsInputWithExpression() { + factory.setParameters(Collections.singletonList(new JpaParameter("spam", null, "foo.toUpperCase()"))); + @SuppressWarnings("unchecked") + ParameterSource source = factory.createParameterSource(Arrays.asList(Collections.singletonMap("foo", "bar"), + Collections.singletonMap("foo", "bucket"))); + String expression = "spam"; + assertTrue(source.hasValue(expression)); + assertEquals("[BAR, BUCKET]", source.getValue(expression).toString()); + } + + @Test + public void testPositionalStaticParameters() { + List parameters = new ArrayList(); + parameters.add(new JpaParameter("foo", null)); + parameters.add(new JpaParameter("bar", null)); + factory.setParameters(parameters); + + PositionSupportingParameterSource source = factory.createParameterSource("not important"); + + String position0 = (String) source.getValueByPosition(0); + String position1 = (String) source.getValueByPosition(1); + + assertEquals("foo", position0); + assertEquals("bar", position1); + } + + @Test + public void testPositionalExpressionParameters() { + List parameters = new ArrayList(); + parameters.add(new JpaParameter(null, "#root.toUpperCase()")); + parameters.add(new JpaParameter("bar", null)); + factory.setParameters(parameters); + + PositionSupportingParameterSource source = factory.createParameterSource("very important"); + + String position0 = (String) source.getValueByPosition(0); + String position1 = (String) source.getValueByPosition(1); + + assertEquals("VERY IMPORTANT", position0); + assertEquals("bar", position1); + } + + @Test + public void testPositionalExpressionParameters2() { + List parameters = new ArrayList(); + + parameters.add(new JpaParameter("bar", null)); + parameters.add(new JpaParameter(null, "#root.toUpperCase()")); + + factory.setParameters(parameters); + + PositionSupportingParameterSource source = factory.createParameterSource("very important"); + + String position0 = (String) source.getValueByPosition(0); + String position1 = (String) source.getValueByPosition(1); + + assertEquals("VERY IMPORTANT", position1); + assertEquals("bar", position0); + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/Consumer.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/Consumer.java new file mode 100644 index 0000000000..b05b267e01 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/Consumer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2002-2012 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.jpa.test; + +import java.util.Collection; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.integration.Message; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public final class Consumer { + + private static final Log logger = LogFactory.getLog(Consumer.class); + + private static final BlockingQueue>> MESSAGES = new LinkedBlockingQueue>>(); + + public void receive(Message>message) { + logger.info("Service Activator received Message: " + message); + MESSAGES.add(message); + } + + public Message> poll(long timeoutInMillis) throws InterruptedException { + return MESSAGES.poll(timeoutInMillis, TimeUnit.MILLISECONDS); + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/JpaTestUtils.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/JpaTestUtils.java new file mode 100644 index 0000000000..80d2f8087a --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/JpaTestUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright 2002-2012 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.jpa.test; + +import java.util.Calendar; +import java.util.Date; + +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.integration.MessageChannel; +import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean; +import org.springframework.integration.core.MessageSource; +import org.springframework.integration.endpoint.SourcePollingChannelAdapter; +import org.springframework.integration.jpa.test.entity.Gender; +import org.springframework.integration.jpa.test.entity.StudentDomain; +import org.springframework.integration.scheduling.PollerMetadata; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public final class JpaTestUtils { + + public static StudentDomain getTestStudent() { + + Calendar dateOfBirth = Calendar.getInstance(); + dateOfBirth.set(1984, 0, 31); + + StudentDomain student = new StudentDomain() + .withFirstName("First Executor") + .withLastName("Last Executor") + .withGender(Gender.MALE) + .withDateOfBirth(dateOfBirth.getTime()) + .withLastUpdated(new Date()); + + return student; + } + + public static SourcePollingChannelAdapter getSourcePollingChannelAdapter(MessageSource adapter, + MessageChannel channel, + PollerMetadata poller, + GenericApplicationContext context, + ClassLoader beanClassLoader) throws Exception { + + SourcePollingChannelAdapterFactoryBean fb = new SourcePollingChannelAdapterFactoryBean(); + fb.setSource(adapter); + fb.setOutputChannel(channel); + fb.setPollerMetadata(poller); + fb.setBeanClassLoader(beanClassLoader); + fb.setAutoStartup(false); + fb.setBeanFactory(context.getBeanFactory()); + fb.afterPropertiesSet(); + + return fb.getObject(); + } +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestTrigger.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestTrigger.java new file mode 100644 index 0000000000..129f8c736c --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/TestTrigger.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2012 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.jpa.test; + +import java.util.Date; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.TriggerContext; + +/** + * + * @author Gunnar Hillert + * @since 2.2 + * + */ +public class TestTrigger implements Trigger { + + private static final AtomicBoolean hasRun = new AtomicBoolean(); + + private final Date executionTime; + + private static volatile CountDownLatch latch = new CountDownLatch(1); + + + public TestTrigger() { + super(); + executionTime = new Date(); + } + + public Date nextExecutionTime(TriggerContext triggerContext) { + + if (TestTrigger.hasRun.getAndSet(true)) { + TestTrigger.latch.countDown(); + return null; + } + + return this.executionTime; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((executionTime == null) ? 0 : executionTime.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TestTrigger other = (TestTrigger) obj; + if (executionTime == null) { + if (other.executionTime != null) + return false; + } else if (!executionTime.equals(other.executionTime)) + return false; + return true; + } + + public void reset() { + TestTrigger.latch = new CountDownLatch(1); + TestTrigger.hasRun.set(false); + } + + public void await() { + try { + TestTrigger.latch.await(5000, TimeUnit.MILLISECONDS); + if (latch.getCount() != 0) { + throw new RuntimeException("test latch.await() did not count down"); + } + } + catch (InterruptedException e) { + throw new RuntimeException("test latch.await() interrupted"); + } + } + +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/Gender.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/Gender.java new file mode 100644 index 0000000000..83fe085578 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/Gender.java @@ -0,0 +1,54 @@ +/* + * Copyright 2002-2012 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.jpa.test.entity; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the gender of the person + * + * @author Amol Nayak + * + */ +public enum Gender { + + MALE("M"),FEMALE("F"); + + private String identifier; + private static Map identifierMap; + + private Gender(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + static { + EnumSet all = EnumSet.allOf(Gender.class); + identifierMap = new HashMap(); + for(Gender gender:all) { + identifierMap.put(gender.getIdentifier(), gender); + } + } + + public static Gender getGenderFromIdentifier(String identifier) { + return identifierMap.get(identifier); + } +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentDomain.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentDomain.java new file mode 100644 index 0000000000..6451b261fe --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentDomain.java @@ -0,0 +1,151 @@ +/* + * Copyright 2002-2012 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.jpa.test.entity; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.NamedNativeQuery; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + + +/** + * The JPA Entity for the Student class + * + * @author Amol Nayak + * @author Gunnar Hillert + * + */ +@Entity(name="Student") +@Table(name="Student") +@NamedQueries({ + @NamedQuery(name="selectStudent", query="select s from Student s where s.lastName = 'Last One'"), + @NamedQuery(name="updateStudent", query="update Student s set s.lastName = :lastName, s.lastUpdated = :lastUpdated where s.rollNumber in (select max(a.rollNumber) from Student a)") +}) +@NamedNativeQuery(resultClass=StudentDomain.class, name="updateStudentNativeQuery", query="update Student s set s.lastName = :lastName, lastUpdated = :lastUpdated where s.rollNumber in (select max(a.rollNumber) from Student a)") +public class StudentDomain { + + @Id + @Column(name="rollNumber") + @GeneratedValue(strategy=GenerationType.AUTO) + private Long rollNumber; + + @Column(name="firstName") + private String firstName; + + @Column(name="lastName") + private String lastName; + + @Column(name="gender") + private String gender; + + @Column(name="dateOfBirth") + @Temporal(TemporalType.DATE) + private Date dateOfBirth; + + @Column(name="lastUpdated") + @Temporal(TemporalType.TIMESTAMP) + private Date lastUpdated; + + public Long getRollNumber() { + return rollNumber; + } + + public void setRollNumber(Long rollNumber) { + this.rollNumber = rollNumber; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public Gender getGender() { + return Gender.getGenderFromIdentifier(gender); + } + + public void setGender(Gender gender) { + this.gender = gender.getIdentifier(); + } + + public Date getDateOfBirth() { + return dateOfBirth; + } + + public void setDateOfBirth(Date dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + + + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + + //Convenience methods for chaining method calls + + public StudentDomain withRollNumber(Long rollNumber) { + setRollNumber(rollNumber); + return this; + } + + public StudentDomain withFirstName(String firstName) { + setFirstName(firstName); + return this; + } + + public StudentDomain withLastName(String lastName) { + setLastName(lastName); + return this; + } + + public StudentDomain withGender(Gender gender) { + setGender(gender); + return this; + } + + public StudentDomain withDateOfBirth(Date dateOfBirth) { + setDateOfBirth(dateOfBirth); + return this; + } + + public StudentDomain withLastUpdated(Date lastUpdated) { + setLastUpdated(lastUpdated); + return this; + } +} diff --git a/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentReadStatus.java b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentReadStatus.java new file mode 100644 index 0000000000..e16ea225a4 --- /dev/null +++ b/spring-integration-jpa/src/test/java/org/springframework/integration/jpa/test/entity/StudentReadStatus.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2012 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.jpa.test.entity; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * The Entity for Student read status + * + * @author Amol Nayak + * + */ +@Entity +@Table(name="StudentReadStatus") +public class StudentReadStatus { + + @Id + @Column(name="rollNumber") + private int rollNumber; + + @Column(name="readAt") + @Temporal(TemporalType.TIMESTAMP) + private Date readAt; + + public int getRollNumber() { + return rollNumber; + } + + public void setRollNumber(int rollNumber) { + this.rollNumber = rollNumber; + } + + public Date getReadAt() { + return readAt; + } + + public void setReadAt(Date readAt) { + this.readAt = readAt; + } + +} diff --git a/spring-integration-jpa/src/test/resources/H2-CreateTables.sql b/spring-integration-jpa/src/test/resources/H2-CreateTables.sql new file mode 100644 index 0000000000..ae8736fe45 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/H2-CreateTables.sql @@ -0,0 +1,7 @@ +drop table StudentReadStatus; +drop table Student; +DROP TABLE OPENJPA_SEQUENCE_TABLE; + +CREATE TABLE OPENJPA_SEQUENCE_TABLE (ID TINYINT NOT NULL, SEQUENCE_VALUE BIGINT, PRIMARY KEY (ID)); +CREATE TABLE Student (rollNumber BIGINT generated by default as identity, dateOfBirth DATE, firstName VARCHAR(255), gender VARCHAR(255), lastName VARCHAR(255), lastUpdated TIMESTAMP, PRIMARY KEY (rollNumber)); +CREATE TABLE StudentReadStatus (rollNumber INTEGER NOT NULL, readAt TIMESTAMP, PRIMARY KEY (rollNumber)); diff --git a/spring-integration-jpa/src/test/resources/H2-DropTables.sql b/spring-integration-jpa/src/test/resources/H2-DropTables.sql new file mode 100644 index 0000000000..c9566ad4b1 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/H2-DropTables.sql @@ -0,0 +1,2 @@ +drop table StudentReadStatus; +drop table Student; diff --git a/spring-integration-jpa/src/test/resources/H2-PopulateData.sql b/spring-integration-jpa/src/test/resources/H2-PopulateData.sql new file mode 100644 index 0000000000..77702e9c82 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/H2-PopulateData.sql @@ -0,0 +1,6 @@ +insert into Student(rollNumber, firstName,lastName, gender, dateOfBirth,lastUpdated) +values ('1001', 'First One','Last One','M','1980-1-1',NOW()); +insert into Student(rollNumber, firstName,lastName, gender, dateOfBirth,lastUpdated) +values ('1002', 'First Two','Last Two','F','1984-1-1',NOW()); +insert into Student(rollNumber, firstName,lastName, gender, dateOfBirth,lastUpdated) +values ('1003', 'First Three','Last Three','F','1984-3-3',NOW()); \ No newline at end of file diff --git a/spring-integration-jpa/src/test/resources/META-INF/persistence.xml b/spring-integration-jpa/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..7efd431749 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,7 @@ + + + + org.springframework.integration.jpa.test.entity.StudentDomain + org.springframework.integration.jpa.test.entity.StudentReadStatus + + diff --git a/spring-integration-jpa/src/test/resources/commonJpa-context.xml b/spring-integration-jpa/src/test/resources/commonJpa-context.xml new file mode 100644 index 0000000000..055d504eec --- /dev/null +++ b/spring-integration-jpa/src/test/resources/commonJpa-context.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml b/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml new file mode 100644 index 0000000000..1908a888a6 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/hibernateJpa-context.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/spring-integration-jpa/src/test/resources/log4j.properties b/spring-integration-jpa/src/test/resources/log4j.properties new file mode 100644 index 0000000000..1fb66a8469 --- /dev/null +++ b/spring-integration-jpa/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=WARN, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - <%m>%n + +log4j.category.org.springframework.integration=WARN +log4j.category.org.springframework.integration.jpa=INFO \ No newline at end of file diff --git a/src/reference/docbook/index.xml b/src/reference/docbook/index.xml index 302a338392..01f1c9546b 100644 --- a/src/reference/docbook/index.xml +++ b/src/reference/docbook/index.xml @@ -140,6 +140,7 @@ + diff --git a/src/reference/docbook/jpa.xml b/src/reference/docbook/jpa.xml new file mode 100644 index 0000000000..c10b5b932d --- /dev/null +++ b/src/reference/docbook/jpa.xml @@ -0,0 +1,1074 @@ + + + JPA Support + + Spring Integration's JPA (Java Persistence API) module provides components + for performing various database operations using JPA. The following + components are provided: + + + + Outbound Channel adapter + + + Outbound Gateway + + + Inbound Channel Adapter + + + + These components can be used to perform + select, + create, + update and + delete + operations on the targeted database by sending/receiving messages to them. + + + The above operations can be performed using either one of the of the + following: + + + + Entity classes + + + + + Java Persistence Query Language (JPQL) for update, select and + delete (inserts are not supported by JPQL) + + + + + Native query + + + Named query + + + + In the following sections we will describe each of these components in + more detail. + +
+ Supported Persistence Providers + + The Spring Integration JPA support is being tested using the following + persistence providers: + + + + Hibernate + + + OpenJPA + + + + EclipseLink + + +
+
+ Java Implementation + Each of the provided components will use the + org.springframework.integration.jpa.core.JpaExecutor + class which in turn will use an implementation of the + org.springframework.integration.jpa.core.JpaOperations + interface. JpaOperations operates like a + typical Data Access Object (Dao) and provides methods such as + find, + persiste, + executeUpdate etc. For most use-cases the provided + default implementation + org.springframework.integration.jpa.core.DefaultJpaOperations + should be sufficient. Nevertheless, the provided components allow you to + optionally specify your own implementation in case you require custom + behavior. + + + That means, that for initializing a JpaExecutor + you have to use one of 3 available constructors that accept either a: + + + + EntityManagerFactory + + + EntityManager or + + + + JpaOperations + + + Example + + The following example of an JPA Outbound Gateway is purely configured + through Java. In typical usage scenarios you will most likely prefer + the XML Namespace Support described further below. However, the example + illustrates how the classes are wired up. + + + First, we instantiate a JpaExecutor using an + EntityManager as constructor argument. + The JpaExecutor is then used as constructor argument for the + o.s.i.jpa.outbound.JpaOutboundGateway and the + JpaOutboundGateway will be passed as constructor + argument in to the EventDrivenConsumer. + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + When using XML Namespace Support the unlerying parser classes will + instantiate the classes for you. Thus, you typically don't have to + deal with the inner workings but in case you need to debug the JPA + Components of your message flow, or if you need to provide customization + understanding it will be helpful. The following section will describe + how to use the XML Namespace Support to configure the Jpa components. + +
+
+ Common Configuration Attributes + + Certain configuration parameters are shared among all Jpa components and are described below: + + + auto-startup + + Lifecycle attribute signaling if this component should + be started during Application Context startup. + Defaults to true. + Optional. + + + id + + Identifies the underlying Spring bean definition, which + is an instance of either EventDrivenConsumer + or PollingConsumer. + Optional. + + + entity-manager-factory + + The reference to the JPA Entity Manager Factory + that will be used by the adapter to create the EntityManager. + Either this attribute or the enity-manager attribute + or the jpa-operations attribute must be provided. + + + entity-manager + + The reference to the JPA Entity Manager that will be used by + the component. + Either this attribute or the enity-manager-factory attribute + or the jpa-operations attribute must be provided. + + + Usually your Spring Application Context only defines a + JPA Entity Manager Factory and the EntityManager is injected using + the @PersistenceContext annotation. This however is not applicable in + regards to the Spring Integration Jpa components. + + Usually, injecting the JPA Entity Manager Factory will be best but in + case you want to inject an EntityManager explicitly, you have to define + a SharedEntityManagerBean. For more information, + please see the relevant + JavaDoc. + + + +]]> + jpa-operations + + Reference to a bean implementing the + JpaOperations interface. In rare cases + it might be advisable to provide your own implementation + of the JpaOperations interface, instead + of relying on the default implementation + org.springframework.integration.jpa.core.DefaultJpaOperations. + As JpaOperations wraps the necessay + datasource; the JPA Entity Manager or JPA Entity Manager Factory + must not be provided, if the jpa-operations + attribute is used. + + + entity-class + + The reference to the JPA Persistence Entity. The exact sermantics of + this attribute vary, depending wether we are performing a persist/update + operation or wether we are retrieving objects from the database. + + + When retrieving data, you can specify the + entity-class attribute to indicate that you would + like to retrieve objects of this type from the database. In that case + you must not define any of the query attributes ( + jpa-query, + native-query or + named-query + ) + + + When persisting data, the entity-class attribute + will indicate the type of object to persist. If not specified + (for persist operations) the entity class will be automatically retrieved + from the Message's payload. + + + jpa-query + + Defines the JPA query (Java Persistence Query Language) to be used. + + + native-query + + Defines the native SQL query to be used. + + + named-query + + This attribute refers to a named query. A named query can + either be defined in Native SQL or JPAQL but the underlying JPA + persistence provider handles that distinction internally. + + +
+ +
+ Providing JPA Query Parameters + + For providing parameters, the JPA Parameter XML sub-element can be used. + It provides a mechanism to provide parameters for the queries that are + either based on the Java Persistence Query Language (JPQL) or native SQL + queries. Parameters can also be provided for Named Queries. + + + Expression based Parameters + ]]> + + Value based Parameters + ]]> + + Positional Parameters + +]]> +
+ +
+ Transaction Handling + + TBD + +
+ +
+ Outbound Channel Adapter + + The JPA Outbound channel adapter allows you to accept messages via a + request channel. The payload can either be used to persist it to the + database or the Message payload and its headers can be used as parameters + for a defined JPQL query to be executed. + + In the following sub sections we shall see what those possible ways of performing + these operations are. + +
+ Using an Entity Class + + The XML snippet below shows how we can use the Outbound Channel + Adapter to persist an entity to the database. + + ]]> + + + + The channel over which a valid JPA entity will be + sent to the JPA Outbound Channel Adapter. + + + + + The fully qualified name of the entity class that + would be accepted by the adapter to be persisted + in the database. You can actually leave off this + attribute in most cases as the adapter can determine + the entity class automatically from the Spring Integration + Message payload. + + + + + The operation that needs to be done by the adapter, valid values are + PERSIST, MERGE + and DELETE. The default value + is MERGE. + + + + + The JPA entity manager to be used. + + + + + As we can see above these 4 attributes of the outbound-channel-adapter + are all we need to configure it to accept entities over the input channel and process + them to PERSIST,MERGE or DELETE + it from the underlying data source. + +
+
+ Using JPA Query Language (JPA QL) + + We have seen in the above sub section how to perform a PERSIST action using an entity + We will now see how to use the outbound channel adapter which uses JPA QL (Java Persistence API Query Language) + + ]]> ]]> +]]> + + + + The input channel over which the message is being sent to the outbound + channel adapter + + + + + The JPA QL that needs to be executed.This query may contain parameters that will be evaluated + using the jpa:param child tag. + + + + + The entity manager used by the adapter to perform the JPA operations + + + + + This sub element, one for each parameter will be used to evaluate the value of + the parameter names specified in the JPA QL specified in the query attribute + + + + + We will see a bit more about the param sub element here. The param sub element + accepts an attribute name which corresponds to the named parameter specified + in the provided JPA QL (point 2 in the above mentioned sample). The value of the parameter can either be static or can be derived + using an expression. The static value and the expression to derive the value is specified using + the value and the expression attributes respectively. These attributes + are mutually exclusive. + + + If the value attribute is specified we can provide an optional + type attribute. The value of this attribute is the fully qualified name of the class + whose value is represented by the value attribute. By default + the type is assumed to be a java.lang.String. + + + + +]]> + + As seen in the above snippet, it is perfectly valid to use multiple param sub elements within an outbound channel adapter + tag and derive some parameters using expressions and some with static value. However, care should + be taken not to specify the same parameter name multiple times, and, provide one param sub element for + each named parameter specified in the JPA query. For example, we are specifying two parameters + level and name where level attribute is a static value of type + java.lang.Integer, where as the name attribute is derived from the payload of the message + + + Though specifying select is valid for JPA QL, it makes no sense as outbound channel adapters will not be + returning any result. If you want to select some values, consider using the outbound gateway instead. + + +
+
+ Using Native query + + + In this section we will see how to use native queries to perform the operations using + JPA outbound channel adapter. Using native queries is similar to using JPA QL, + except that the query specified here is a native database query. By choosing + native queries we lose the database vendor independence which we get using JPA QL. + + + One of the things we can achieve using native queries is to perform database inserts, which + is not possible using JPA QL (To perform inserts we send JPA entities to the channel adapter as we have seen earlier). + Below is a small xml fragment that demonstrates the use of native query to insert values in a table. Please note that we + have only mentioned the important attributes below. All other attributes like channel, + entity-manager and the param sub element have the same semantics as when we use + JPA QL. + + + Please be aware that named parameters may not be supported + by your JPA provider in conjunction with native SQL queries. + While they work fine using Hibernate, OpenJPA and EclipseLink + do NOT support them: https://issues.apache.org/jira/browse/OPENJPA-111 + + Section 3.8.12 of the JPA 2.0 spec states: "Only positional + parameter binding and positional access to result items may + be portably used for native queries." + + + + +]]> + + + + The native query that will be executed by this outbound channel adapter + + + + + The flag that indicates whether the specified query is a JPA QL or a native query. Not specifying this attribute + or setting it's value to false will lead to the value specified in the query attribute + to be evaluated as a JPA QL. + + + + + + TODO: The above xml declaration will change and the native-query may no longer hold the flag but will become + the native query itself. However, the document is as per the code currently. Change this when the changes are made in the code + + + + +
+
+ Using Named query + + + We will now see how to use named queries after seeing using entity, JPA QL and native query in previous sub sections. + Using named query is also very similar to using JPA QL or a native query, except that we specify a named query instead of a query. + Before we go further and see the xml fragment for the declaration of the outbound-channel-adapter, we will + see how named JPA named queries are defined. + + + In our case, if we have an entity called Student, then we have the following in the class to define + two named queries selectStudent and updateStudent. Below is a way to define + named queries using annotations + + + + + You can alternatively use the orm.xml to define named queries as seen below + + + ... + + select s from Student s where s.lastName = 'Last One' + +]]> + + Now that we have seen how we can define named queries using annotations or using orm.xml, we + will now see a small xml fragment for defining an outbound-channel-adapter using named query + + + + +]]> + + + + The named query that we want the adapter to execute when it receives a message over the channel + + + + +
+ + We have now seen four possible ways of defining the outbound-channel-adapter in the previous sub sections. + We will now see how to use outbound gateways in the next section. + +
+ Configuration Parameter Reference + + + + +]]> +
+
+
+ Outbound Gateway + + + Outbound gateways are similar to outbound channel adapter except that it can also be used to + get a result on the reply channel after performing + the given JPA operation . If you are directly referring to this outbound gateway section, + we would recommend you to first go through the outbound channel adapter section given above, as most of the common concepts have been + explained there. + + + Simlar to the outbound-channel-adapter, we can use + + + Entity classes + + + + JPA Query Language (JPQL) + + + + Native query + + + Named query + + + for performing various JPA operations. We will be seeing each of these in the following four sub sections. Since we are assuming + you are already familiar with the outbound-channel-adapter, we will only discuss portions relevant to + outbound-gateway. + + +
+ Difference between <emphasis>UPDATING</emphasis> and <emphasis>RETRIEVING</emphasis> gateway + + Before we continue, let us see what are the types of JPA outbound gateways. + JPA outbound gateways are either UPDATING or RETRIEVING + types. The type is specified using the gateway-type attribute. + If this attribute is not specified, the gateway type if defaulted to an UPDATING + type of the gateway + + + Whenever the gateway intends to perform an action that updates or deletes some records in the + database using JPA, you need to use an UPDATING type of gateway. If an entity is + used, a merged/persisted entity is returned. In any other case + the number of records affected (updated or deleted) are returned. + + + If the calling application requires to select/retrieve some data from the database + using outbound-gateway, we use a RETRIEVING type + of gateway. With a RETRIEVING type of gateway, we can use either + of JPA QL, Named Query or Native Query for selecting the data and retrieving the result. + +
+
+ Using Entity class + + We will see below an xml snippet that declares an outbound-gateway using + entity class. + + ]]> + + + This is the request channel for the outbound gateway, this is similar + to the channel attribute of the outbound-channel-adapter + + + + + This is where a gateway differs from an outbound adapter, this is the channel over + which the reply of the JPA operation performed is received. If,however, you are not interested in the + reply received and just want to perform the operation, then outbound-channel-adapter + is an appropriate choice. In above case, where we are using entity class, the reply will + be the entity object that was created/merged as a result of the JPA operation performed. + + + + Valid values are RETRIEVING and UPDATING. + This attribute is optional and in it's absence the value + defaults to UPDATING. + + + +
+ +
+ Using JPA Query Language (JPA QL) + + We will now see how we can use JPA QL in an outbound gateway. Below xml snippet is a declaration of the + outbound-gateway. + + + + +]]> + + + + The JPA QL that will be executed by the gateway. Since the + gateway-type is UPDATING, only update and + delete JPA QL will be acceptable. + + + + + On sending a message with string payload and containing a header rollNumber + with a long value, the last name of the student with the provided roll number + is updated to the value provided in the message payload. When using a gateway of type + UPDATING, the return value is always an integer + value which denotes the number of records affected by execution of the JPA QL. + + + TODO: Show one RETRIEVING type of gateway sample, also somewhere in the manual + show a sample usage of BeanPropertyParameterSource + +
+
+ Using Native query + + Using native query is very identical to using the JPA QL except that the query attribute now + holds the native SQL Query and an additional attribute native-query set to + true + (TODO: Change this description once the change for the attribute names is done) + +
+
+ Using Named query + + Using named query is also very similar to using a JPA QL except that we have + the named-query attribute as seen in the xml snippet below + + + + +]]> +
+ +
+ Configuration Parameter Reference + + + + +]]> +
+
+
+ Inbound Channel Adapter + + An inbound channel adapter is used to execute a select query over the + database using JPA QL and return the result. The message payload will be either a single + entity or a List of entities. Below is a sample xml snippet that shows + a sample usage of inbound-channel-adapter. + + ]]> + + +]]> + + + + + The channel over which the inbound-channel-adapter will put the + messages with the payload received after executing the provided JPA QL in the + query attribute. + + + + + The EntityManager instance that will be used to perform the + required JPA operations. + + + + + Attribute signalling if the component should be automatically started on startup of + the Application Context. The value defaults to true + + + + + The JPA QL that needs to be executed and whose result needs to be sent out as the + payload of the message + + + + + The attribute that tells if the executed JPA QL gives a single entity in the result + or a List of entities. If the value is set to true, + the single entity retrieved is sent as the payload of the message. If, however, multiple + results are returned after setting this to true, a + MessageHandlingException is thrown. The value defaults to false + + + + + The maximum number of rows that should be retrieved on execution of the given JPA QL. + Relevant only if the query can potentially receive multiple records + + + + + + Set this value to true if you want + to delete the rows received after execution of the query. + Please ensure that the component is operating as part + of a transaction. + + Otherwise, you may encounter an Exception such as: + java.lang.IllegalArgumentException: Removing + a detached instance ... + + + +
+ Configuration Parameter Reference + + + + +]]> +
+
+
+ JPA Adapters XML attributes quick reference +
+ Common XML Attributes + + Common Attributes + + + + + + + Name + Description + Mandatory + + + + + entity-manager + An instance of javax.persistence.EntityManager + that will be used to perform the JPA operations. + + No + + + entity-manager-factory + An instance of javax.persistence.EntityManagerFactory + that will be used to obtain an instance of javax.persistence.EntityManager + that will perform the JPA operations. Either of entity-manager-factory and + entity-manager attributes is mandatory. + + No + + + jpa-operations + + An implementation of + org.springframework.integration.jpa.core.JpaOperations + that would be used to perform the JPA operations. It is recommended not to + provide an implementation of your own but use the default + org.springframework.integration.jpa.core.DefaultJpaOperations + implementation. + + + No + + + query + The JPA QL that needs to be executed by this adapter + No + + + native-query + + The boolean flag that indicates that the string value given in query attribute + is a native query. By default the value is false + (TODO: change this after the changes are made in adapter) + + No + + + named-query + The Named JPA QL that needs to be executed by this adapter + No + + + +
+
+
+ Outbound adapter/gateway XML Attributes + + Outbound adapter/gateway Attributes + + + + + + + Name + Description + Mandatory + + + + + channel + + The channel over which the outbound adapter will receive messages for performing the desired operation. + This attribute is relevant for outbound-channel-adapter only. + + Yes + + + request-channel + + The channel over which the outbound gateway will receive messages for performing the desired operation. + This attribute is relevant for outbound-gateway only. This attribute is similar to + channel attribute of the outbound-channel-adapter + + Yes + + + entity-class + + The fully qualified name of the entity class of the entities that would be sent to this adapter to perform JPA Operation + using entity. The attributes entity-class, query and named-query + are mutually exclusive. + + No + + + reply-channel + + The channel over which the gateway will send the response after + performing the required JPA operation. This attribute is relevant for outbound-gateway + only. If this attribute is not defined, the request message must have a + replyChannel header + + No + + + persist-mode + + Accepts one of the PERSIST,MERGE + or DELETE. Indicates the operation that the adapter needs to + perform. Relevant only if an entity is being used for JPA operations. Ignored if + JPA QL, named query or native query is provided. Defaults to MERGE + + No + + + gateway-type + + Valid values are UPDATING and RETRIEVING, + the value defaults to UPDATING. The difference between these types + of gateways is explained earlier in the manual. This attribute is relevant to + outbound-gateway only + + No + + + parameter-source-factory + + An instance of org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory + that will be used to get an instance of + org.springframework.integration.jpa.support.parametersource.ParameterSource which will be used to + resolve the values of the parameters provided in the query. Ignored if operations are done using JPA entity. + If a param sub element is used, the factory must be of type + org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory + + No + + + +
+
+
+ Inbound adapter XML Attributes + + + + + + + + Name + Description + Mandatory + + + + + channel + + The channel over which the adapter will send a message with the payload + that was received after performing desired the JPA operation + + Yes + + + delete-after-poll + + A boolean flag that indicates whether the + records selected are to be deleted after they are being polled by the adapter. + By default the value is false, that is, the + records will not be deleted. Please ensure that + the component is operating as part of a transaction. + + Otherwise, you may encounter an Exception such as: + + java.lang.IllegalArgumentException: Removing a detached instance ... + + No + + + delete-per-row + + A boolean flag that indicates whether the records can be deleted in bulk + or are deleted one record at a time. By default the value is false, that is, + the records are bulk deleted + + No + + + max-rows + + 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. + + No + + + expect-single-result + + A boolean flag indicates whether the select operation gives a single + result or a List or results. If this flag is set to true, + the single entity selected is sent as the payload of the message. + If however, multiple entities are selected, an exception is thrown. + If false, the List of entities is being + sent as the payload of the message. Even a single entity will be sent a + List or size one when the value is set to false. + By default the value is false + + No + + + +
+
+
+ + You can find more samples for using spring integration's JPA adapter at: + https://github.com/SpringSource/spring-integration-samples/tree/master/basic/jpa + +