How to get Database connection in Spring using JUnit? - java

I'm trying to test the correct information given from DB using the services and the correct logic of the methods. In this simple example, I'm only using the statement assertEquals to compare the id given for the roleService, but I'm still getting errors. I have the following code:
[UPDATED]
Test method:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(value = { "classpath:applicationContext.xml" })
#Transactional
#WebAppConfiguration
#ComponentScan(basePackages ={ "com.project.surveyengine" },
excludeFilters = {#ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class) ,
#ComponentScan.Filter(type = FilterType.ANNOTATION, value = WebConfig.class)})
public class RoleServiceTest {
#Configuration
static class ContextConfiguration {
#Bean
public RoleService roleService() {
RoleService roleService = new RoleService();
// set properties, etc.
return roleService;
}
}
private final LocalServiceTestHelper helper =
new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()
.setDefaultHighRepJobPolicyUnappliedJobPercentage(100));
private Closeable closeable;
#Before
public void setUp() {
helper.setUp();
ObjectifyService.register(Role.class);
closeable = ObjectifyService.begin();
}
#After
public void tearDown() {
closeable.close();
helper.tearDown();
}
#Autowired
private RoleService roleService;
#Test
public void existsRole() throws Exception{
Role role = roleService.getByName("ROLE_ADMIN");
assertEquals("Correct test", Long.valueOf("5067160539889664"), role.getId());
}
}
RoleService is the service class that consult Database information using objectifyService from Google App Engine.
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config />
<context:component-scan base-package="com.project.surveyengine.config"/>
<!--Controllers-->
<bean id="inboxController" class="com.project.surveyengine.controller.InboxController" autowire="byType"></bean>
<bean id="mailController" class="com.project.surveyengine.controller.MailController" autowire="byType"></bean>
<bean id="loginController" class="com.project.surveyengine.controller.LoginController" autowire="byType"></bean>
<bean id="initController" class="com.project.surveyengine.controller.InitController" autowire="byType"></bean>
<!--Services-->
<bean id="answerService" class="com.project.surveyengine.service.impl.AnswerService" autowire="byType"></bean>
<bean id="blobService" class="com.project.surveyengine.service.impl.BlobService" autowire="byType"></bean>
<bean id="mailService" class="com.project.surveyengine.service.impl.MailService" autowire="byType"></bean>
<bean id="caseService" class="com.project.surveyengine.service.impl.CaseService" autowire="byType"></bean>
<bean id="roleService" class="com.project.surveyengine.service.impl.RoleService" autowire="byType"></bean>
</beans>
Into the com.project.surveyengine.config I have the folliwing three classes:
1)
public class MyXmlWebApplicationContext extends XmlWebApplicationContext {
protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
super.initBeanDefinitionReader(beanDefinitionReader);
if (SystemProperty.environment.value() == SystemProperty.Environment.Value.Production) {
beanDefinitionReader.setValidating(false);
beanDefinitionReader.setNamespaceAware(true);
}
}
}
2)
#Configuration
#EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter{
#Bean
UrlBasedViewResolver resolver(){
UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setPrefix("/views/");
resolver.setSuffix(".jsp");
resolver.setViewClass(JstlView.class);
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("/statics/");
}
}
3)
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityContext extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/statics/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
//...more code
}
}
I'm getting this errors:
Dec 20, 2016 4:40:03 PM
com.google.appengine.api.datastore.dev.LocalDatastoreService init
INFO: Local Datastore initialized: Type: High Replication Storage:
In-memory
java.lang.NullPointerException at
com.project.surveyengine.service.impl.RoleServiceTest.existsRole(RoleServiceTest.java:111)
RoleServiceTest.java:111 = assertEquals("Correct test", Long.valueOf("5067160539889664"), role.getId());

Did you try to add #WebAppConfiguration as suggested in the documentation http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/web/WebAppConfiguration.html ?
I'd wirte your test code in this way
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(value = { "classpath:applicationContext.xml" })
#TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
#Transactional
#WebAppConfiguration
#ComponentScan(basePackages ={ "com.project.surveyengine" }, excludeFilters = {
#ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class) })
public class RoleServiceTest {
#Autowired
private RoleService roleService;
#Test
public void existsRole() throws Exception{
Role role = roleService.getByName("ROLE_ADMIN");
assertEquals("Correct test", Long.valueOf("5067160539889664"), role.getId());
}
}
as you can see i added the annotation #WebAppConfiguration too
I hope this can help you

Related

Spring Boot with XML based security configuration doesn't seem to use correct authentication manager?

We're trying to upgrade our old, mostly XML-based Spring web application, to Spring Boot (2.5.4). We've almost managed to do so but there's one thing that is still nagging us. Even after using Spring Boot we (still for now) have our Spring Security configuration defined like this in XML:
<http pattern="/api/something/v1/**" use-expressions="true" create-session="stateless" authentication-manager-ref="somethingAuthenticationManager">
<http-basic/>
<custom-filter ref="queryParamAuthFilter" before="BASIC_AUTH_FILTER" />
<intercept-url pattern="/api/something/v1/**" access="hasRole('ROLE_SOMETHING')" />
<csrf disabled="true"/>
</http>
The authentication managers are defined like this:
<authentication-manager alias="somethingAuthenticationManager">
<authentication-provider user-service-ref="somethingUserDetailsService">
</authentication-provider>
</authentication-manager>
<authentication-manager id="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService">
</authentication-provider>
</authentication-manager>
The problem is that when I make a GET request to /api/something/v1 it seems like it's using the wrong authentication manager (the one with id authenticationManager) and not the somethingAuthenticationManager. This used to work before Spring Boot. Why is this and how can we resolve it?
Note that we're not using #EnableWebSecurity or any Java configuration related to Spring Security (afaik), everything is defined in our XML file.
Update
We start the application like this:
#EnableRabbit
#EnableRetry
#SpringBootApplication
#EnableAsync
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ImportResource(locations = {"classpath*:META-INF/spring/applicationContext*.xml", "classpath:spring/webflow-config.xml"})
public class OurApplication {
public static void main(String[] args) {
SpringApplication.run(OurApplication.class, args);
}
}
which finds the WebappConfiguration that contains some additional configuration:
#Configuration
#ImportResource("classpath:spring/webmvc-config.xml")
public class WebappConfiguration implements WebMvcConfigurer {
#Bean
public TilesConfigurer tilesConfigurer() {
TilesConfigurer tilesConfigurer = new TilesConfigurer();
String[] defs = {
"classpath:WEB-INF/layouts/layouts.xml",
"WEB-INF/views/**/views.xml"
};
// Scan views directory for Tiles configurations
tilesConfigurer.setDefinitions(defs);
return tilesConfigurer;
}
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
((StandardJarScanner) context.getJarScanner()).setScanManifest(false);
context.setResources(new ExtractingRoot());
}
};
}
#Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainerCustomizer() {
return container -> container.addContextCustomizers(context -> context.setReloadable(false));
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/web-resources/**").addResourceLocations("classpath:/META-INF/web-resources/");
}
#Bean
public TilesViewResolver tilesViewResolver() {
TilesViewResolver tilesViewResolver = new TilesViewResolver();
tilesViewResolver.setRequestContextAttribute("requestContext");
tilesViewResolver.setExposedContextBeanNames("ownerLogoUrlFinder");
return tilesViewResolver;
}
#Bean
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean() {
DispatcherServlet servlet = new DispatcherServlet();
servlet.setApplicationContext(new AnnotationConfigWebApplicationContext());
return new DispatcherServletRegistrationBean(servlet, "/");
}
#Bean
public FilterRegistrationBean<OpenEntityManagerInViewFilter> openEntityManagerInViewFilterRegistrationBean(OpenEntityManagerInViewFilter openEntityManagerInViewFilter) {
FilterRegistrationBean<OpenEntityManagerInViewFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(openEntityManagerInViewFilter);
registration.addUrlPatterns("/*");
registration.setName("openEntityManagerInViewFilter");
registration.setOrder(1);
return registration;
}
#Bean
public OpenEntityManagerInViewFilter openEntityManagerInViewFilter() {
return new OpenEntityManagerInViewFilter();
}
}
Update 2
I get this when starting up:
2021-09-21 13:46:33.933 WARN 46673 --- [ main] o.s.b.f.parsing.FailFastProblemReporter : Configuration problem: Overriding globally registered AuthenticationManager
Offending resource: file [/Users/johan/myproject/target/classes/META-INF/spring/applicationContext-security.xml]
I have seen something similar and solved it by changing alias to id on my authentication-manager after reading this: https://github.com/spring-projects/spring-security/issues/2163

Spring MVC transaction manager defined in root context doesn't open transactions in dao defined in child context

I have faced a real proglem and solved it, but couldn't figure out what has been happened.
I defined transactionManager and sessionFactory bean in root context and my dao class with #Transactional methods in dispatcher context. And that's all. When I was trying to use getCurrentSession() in dao, I was getting "could not obtain a current session".
But, as I can remember, dispatcher context is aware about root context and has access to all beans in root context.
Can somebody explain me, why do not transactions open before #Transactional method if transactionManager and sessionFactory were defined in root context and class with #Transactional in child context?
Database config class
#Configuration
#EnableTransactionManagement
public class DatabaseConfig {
#Bean
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(getDatabaseDataSource());
sessionFactoryBean.setPackagesToScan("com.varguss.domain");
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL57Dialect");
properties.setProperty("hibernate.show_sql", "true");
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.connection.useUnicode", "true");
properties.setProperty("hibernate.connection.characterEncoding", "utf8");
properties.setProperty("hibernate.connection.charSet", "utf8");
sessionFactoryBean.setHibernateProperties(properties);
return sessionFactoryBean;
}
#Bean
#Autowired
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
#Bean(name = "dataSource", destroyMethod = "close")
public BasicDataSource getDatabaseDataSource() throws IOException {
BasicDataSource databaseDataSource = new BasicDataSource();
Properties properties = new Properties();
ClassPathResource propertiesFileResource = new ClassPathResource("database.properties");
properties.load(propertiesFileResource.getInputStream());
databaseDataSource.setDriverClassName(properties.getProperty("driverClassName"));
databaseDataSource.setUrl(properties.getProperty("url"));
databaseDataSource.setUsername(properties.getProperty("username"));
databaseDataSource.setPassword(properties.getProperty("password"));
return databaseDataSource;
}
}
DAO class
#Repository
#Transactional
public class DbComputerPartDAO implements ComputerPartDAO {
private SessionFactory sessionFactory;
private Strategy strategy;
#Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
strategy = StrategyFactory.getStrategy(StrategyType.ALL, sessionFactory);
}
#Override
#Transactional(readOnly = true)
public List<ComputerPart> allParts() {
return sessionFactory.getCurrentSession().createQuery("FROM ComputerPart part ORDER BY part.count DESC", ComputerPart.class).getResultList();
}
#Override
#Transactional(readOnly = true)
public ComputerPart part(Long id) {
return sessionFactory.getCurrentSession().find(ComputerPart.class, id);
}
#Override
public void save(String name, boolean isImportant, Long count) {
sessionFactory.getCurrentSession().saveOrUpdate(new ComputerPart(name, isImportant, count));
}
#Override
public void remove(Long id) {
ComputerPart computerPart = part(id);
if (computerPart != null)
sessionFactory.getCurrentSession().delete(computerPart);
}
#Override
#Transactional(readOnly = true)
public List<ComputerPart> byImportance(boolean isImportant) {
return sessionFactory.getCurrentSession().createQuery("FROM ComputerPart part WHERE part.isImportant ORDER BY part.count DESC", ComputerPart.class).getResultList();
}
#Override
public void updateImportance(Long id, boolean isImportant) {
ComputerPart computerPart = part(id);
if (computerPart != null)
computerPart.setImportant(isImportant);
}
#Override
public void updateName(Long id, String name) {
ComputerPart computerPart = part(id);
if (computerPart != null)
computerPart.setName(name);
}
#Override
public void updateCount(Long id, Long count) {
ComputerPart computerPart = part(id);
if (computerPart != null)
computerPart.setCount(count);
}
#Override
#Transactional(readOnly = true)
public List<ComputerPart> page(int pageNumber) {
return strategy.page(pageNumber);
}
#Override
#Transactional(readOnly = true)
public List<ComputerPart> parts() {
return strategy.parts();
}
#Override
#Transactional(readOnly = true)
public Integer lastPageNumber() {
return strategy.lastPageNumber();
}
#Override
#Transactional(readOnly = true)
public List<ComputerPart> search(String partOfName) {
return strategy.search(partOfName);
}
#Override
public void changeStrategy(StrategyType strategyType) {
this.strategy = StrategyFactory.getStrategy(strategyType, sessionFactory);
}
}
Root context
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<context:annotation-config/>
<bean class="com.varguss.config.DatabaseConfig"/>
</beans>
Child context
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
<!-- Enables the Spring MVC #Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by #Controllers to .jsp resources in the /resources/views/ directory -->
<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="/views/" p:suffix=".jsp" />
<context:component-scan base-package="com.varguss.dao" />
<context:component-scan base-package="com.varguss.controller" />
</beans:beans>
When using hierarchical application context (a parent and child) the child can see the beans from the parent. So it can detect the EntityManagerFactory and the PlatformTransactionManager.
However when using things like AOP that only applies to beans in the same application context as the AOP is defined in. So AOP defined in the parent context only applies to beans in the parent context, not to beans in the child contexts.
So in your case the #EnableTransactionManagement is in the parent context but in there there aren't any beans with #Transactional, those are in the child context. So either create an #Configuration which enables transactions there or use <tx:annotation-driven /> in your XML configuration.

How to convert spring bean integration From XML to Java annotation based Config

I have this xml based configuration. But in my project I want to use java annotation based configuration. How to do the conversion?
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<property name="host" value="mail.csonth.gov.uk"/>
</bean>
<bean id="registrationService" class="com.foo.SimpleRegistrationService">
<property name="mailSender" ref="mailSender"/>
<property name="velocityEngine" ref="velocityEngine"/>
</bean>
<bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean">
<property name="velocityProperties">
<value>
resource.loader=class
class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
</value>
</property>
</bean>
</beans>
Create a class annotated with #Configuration (org.springframework.context.annotation.Configuration) and for each bean declaration in your XML file create a #Bean (org.springframework.context.annotation.Bean) method within this class.
#Configuration
public class MyConfiguration {
#Bean
public JavaMailSenderImpl mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("mail.csonth.gov.uk");
return mailSender;
}
#Bean
public SimpleRegistrationService registrationService(JavaMailSenderImpl mailSender, VelocityEngineFactoryBean velocityEngine) {
SimpleRegistrationService registrationService = new SimpleRegistrationService();
registrationService.setMailSender(mailSender);
registrationService.setVelocityEngine(velocityEngine);
return registrationService;
}
#Bean
public VelocityEngineFactoryBean velocityEngine() {
VelocityEngineFactoryBean velocityEngine = new VelocityEngineFactoryBean();
Properties velocityProperties = new Properties();
velocityProperties.put("resource.loader", "class");
velocityProperties.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.setVelocityProperties(velocityProperties);
return velocityEngine;
}
}
This example assumes that the mailSender and velocityEngine beans are required elsewhere in your application config since this is implied by the XML config you supplied. If this is not the case i.e. if the mailSender and velocityEngine beans are only required to construct the registrationService bean then you do not need to declare the mailSender() and velocityEngine() methods as public nor do you need to annotate then with #Bean.
You can instruct Spring to read this configuration class by
Component scanning e.g. #ComponentScan("your.package.name")
Registering the class with an AnnotationConfigApplicationContext e.g.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(MyConfiguration.class);
context.refresh();
Answer of #glitch was helpful but I got an error at below line.
velocityEngine.setVelocityProperties("resource.loader=class", "class.resource.loader.class=org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
How ever I fixed that. Find below is the full implementation
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import com.vlclabs.adsops.service.SendEmailServiceImpl;
import org.springframework.ui.velocity.VelocityEngineFactoryBean;
import java.util.Properties;
#Configuration
public class EmailConfiguration {
#Bean
public JavaMailSenderImpl mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("mail.csonth.gov.uk");
return mailSender;
}
#Bean
public SendEmailServiceImpl registrationService(JavaMailSenderImpl mailSender, VelocityEngineFactoryBean velocityEngine) {
SendEmailServiceImpl registrationService = new SendEmailServiceImpl();
registrationService.setMailSender(mailSender);
registrationService.setVelocityEngine(velocityEngine.getObject());
return registrationService;
}
#Bean
public VelocityEngineFactoryBean velocityEngine() {
VelocityEngineFactoryBean velocityEngine = new VelocityEngineFactoryBean();
Properties velocityProperties = new Properties();
velocityProperties.put("resource.loader", "class");
velocityProperties.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
velocityEngine.setVelocityProperties(velocityProperties);
return velocityEngine;
}
}

Spring Boot batch plus scheduler

I am new to spring batch so trying to schedule a batch works as a job every 5 seconds using spring boot batch and scheduler but somewhere I am missing something so getting below error.Please help me to solve this error.
Error:-
2017-03-12 02:23:26.614 INFO 8292 --- [pool-1-thread-1] com.spring.test.BatchConfig : The time is now 02:23:26
2017-03-12 02:23:26.617 ERROR 8292 --- [pool-1-thread-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task.
java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
at org.springframework.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:721)
at org.springframework.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)
at org.springframework.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:285)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.enhanceFactoryBean(ConfigurationClassEnhancer.java:384)
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:292)
at com.spring.test.BatchConfig$$EnhancerBySpringCGLIB$$dcfce226.job(<generated>)
at com.spring.test.BatchConfig.reportCurrentTime(BatchConfig.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
application.properties:-
spring.batch.job.enabled=false
spring-config.xml:-
<beans:beans xmlns="http://www.springframework.org/schema/batch"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/batch
http://www.springframework.org/schema/batch/spring-batch-3.0.xsd">
<job id="job">
<step id="step1">
<tasklet>
<chunk reader="itemReader" processor="itemProcessor" writer="itemWriter"
commit-interval="1" />
</tasklet>
</step>
</job>
<beans:bean id="itemReader" class="com.spring.test.Reader" />
<beans:bean id="itemProcessor" class="com.spring.test.Processor" />
<beans:bean id="itemWriter" class="com.spring.test.Writer" />
</beans:beans>
Application:-
#SpringBootApplication
#EnableScheduling
#ComponentScan
#ImportResource("classpath:spring-config.xml")
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class);
}
}
BatchConfig:-
#Configuration
#EnableBatchProcessing
#Import({BatchScheduler.class})
public class BatchConfig {
private static final Logger log = LoggerFactory
.getLogger(BatchConfig.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat(
"HH:mm:ss");
#Autowired
private SimpleJobLauncher jobLauncher;
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Bean
public Job job() {
return jobBuilderFactory.get("job")
.incrementer(new RunIdIncrementer())
.flow(step1())
.end()
.build();
}
#Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.<String, String> chunk(1)
.reader(new Reader())
.processor(new Processor())
.writer(new Writer())
.build();
}
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() throws Exception{
log.info("The time is now {}", dateFormat.format(new Date()));
JobParameters param = new JobParametersBuilder().addString("JobID",
String.valueOf(System.currentTimeMillis())).toJobParameters();
JobExecution execution = jobLauncher.run(job(), param);
System.out.println("Job Execution Status: " + execution.getStatus());
}
}
BatchScheduler:-
#Configuration
#EnableScheduling
public class BatchScheduler {
#Bean
public ResourcelessTransactionManager transactionManager() {
return new ResourcelessTransactionManager();
}
#Bean
public MapJobRepositoryFactoryBean mapJobRepositoryFactory(
ResourcelessTransactionManager txManager) throws Exception {
MapJobRepositoryFactoryBean factory = new
MapJobRepositoryFactoryBean(txManager);
factory.afterPropertiesSet();
return factory;
}
#Bean
public JobRepository jobRepository(
MapJobRepositoryFactoryBean factory) throws Exception {
return factory.getObject();
}
#Bean
public SimpleJobLauncher jobLauncher(JobRepository jobRepository) {
SimpleJobLauncher launcher = new SimpleJobLauncher();
launcher.setJobRepository(jobRepository);
return launcher;
}
}
It is hard to figure out what is actually wrong with your code but it looks like some of your beans do not have no-args constructor.
Configuring batch with scheduler is relatively simple. I've just downloaded the code from official batch guide and extended it with small amount of my own code.
You can try the same:
1) Add spring.batch.job.enabled=false to application.properties file (just like you already have in your project)
2) Add #EnableScheduling to a Application class (just like you have in your code)
3) Create a Component class for scheduling with the next code:
import org.springframework.batch.core.*;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
public class Scheduler {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private Job job;
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() throws Exception{
JobParameters param = new JobParametersBuilder().addString("JobID",
String.valueOf(System.currentTimeMillis())).toJobParameters();
JobExecution execution = jobLauncher.run(job, param);
System.out.println("Job Execution Status: " + execution.getStatus());
}
}
That works well. Hope that having this example you'll figure out what is wrong with your own code.

Lost and "unacked" messages with Spring Integration AMQP and RabbitMQ

I am trying to create 2 simple applications; one is posting messages to RabbitMQ channel and the other one is receiving it from the channel and print them out the console. Sender app starts and post 10 messages immediately.
What I see on the client side console is only about half of the messages printed.
I also see one of the messages always sits in the "Unacked" state when I checked the RabbitMQ web client.
When I read the documents, as far as I understand "amqp inbound/outbound gateway" is an easy way to implement this.
Can you please help me to understand why I am loosing some messages and one sits in the "Unacked" state?
Also, how should I change it to get all the messages on the other side?
Thank you in advance.
Here are the xml configuration and files on sender side:
integrationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/amqp
http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Configuration for Component Scan -->
<context:component-scan base-package="com.amqp.sample" />
<context:property-placeholder location="classpath*:rabbitmq.properties"/>
<int:gateway id="taskGateway" service-interface="com.amqp.sample.TaskGateway" default-request-channel="processChannel" />
<int-amqp:channel id="processChannel"
connection-factory="connectionFactory"
message-driven="true"
queue-name="ha.rabbit.channel" />
<!-- RabbitMQ Connection Factory -->
<rabbit:connection-factory id="connectionFactory"
addresses="${rabbitmq.addresses}"
username="${rabbitmq.username}"
password="${rabbitmq.password}" />
<rabbit:template id="amqpTemplate"
connection-factory="connectionFactory"
reply-timeout="-1" />
<rabbit:admin connection-factory="connectionFactory" />
<int-amqp:outbound-gateway request-channel="processChannel"
reply-channel="processChannel"
reply-timeout="-1" />
</beans>
TaskGateway.java
import org.springframework.messaging.Message;
import com.amqp.sample.model.Task;
public interface TaskGateway {
void processTaskRequest(Message<Task> message);
}
Task.java
import java.io.Serializable;
public class Task implements Serializable {
private static final long serialVersionUID = -2138235868650860555L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Task(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public String toString() {
return "Task [id=" + id + ", name=" + name + "]";
}
}
Application.Java
#PropertySources({
#PropertySource("classpath:application.properties"),
})
#EnableConfigurationProperties
#ComponentScan
#EnableAutoConfiguration
#ImportResource("classpath:integrationContext.xml")
public class Application extends SpringBootServletInitializer {
public static final Logger logger = LoggerFactory.getLogger(Application.class);
private static TaskGateway taskGateway;
public static void main(String[] args) {
ApplicationContext context=SpringApplication.run(Application.class, args);
taskGateway = context.getBean(TaskGateway.class);
for(int i=0; i<10; i++){
Message<Task> message = MessageBuilder.withPayload(getTask(i)).build();
taskGateway.processTaskRequest(message);
}
}
/**
* Creates a sample task returns.
*
* #return Task
*/
private static Task getTask(final int id) {
return new Task(id, "Task with ID:" + id);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
And, here are the files on receiver side:
integrationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/amqp
http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- Configuration for Component Scan -->
<context:component-scan base-package="com.amqp.sample" />
<context:property-placeholder location="classpath*:rabbitmq.properties"/>
<!-- RabbitMQ Connection Factory -->
<rabbit:connection-factory id="connectionFactory"
addresses="${rabbitmq.addresses}"
username="${rabbitmq.username}"
password="${rabbitmq.password}" />
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:admin connection-factory="connectionFactory" />
<int:channel id="inputChannel"/>
<int-amqp:inbound-gateway request-channel="inputChannel" reply-channel="inputChannel"
queue-names="ha.rabbit.channel"
connection-factory="connectionFactory"
amqp-template="amqpTemplate"/>
<int:service-activator input-channel="inputChannel" ref="taskProcessService" method="process" />
</beans>
ProcessService.java
import org.springframework.messaging.Message;
public interface ProcessService<T> {
/**
* Processes incoming message(s)
*
* #param message SI Message.
*/
void process(Message<T> message);
}
TaskProcessService
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Component;
import com.amqp.sample.model.Task;
#Component("taskProcessService")
public class TaskProcessService implements ProcessService<Task> {
private final Logger logger = LoggerFactory.getLogger(TaskProcessService.class);
#Override
public void process(Message<Task> message) {
logger.info("Received Message : " + message.getPayload());
}
}
Application.java
#PropertySources({
#PropertySource("classpath:application.properties"),
})
#EnableConfigurationProperties
#ComponentScan
#EnableAutoConfiguration
#ImportResource("classpath:integrationContext.xml")
public class Application extends SpringBootServletInitializer {
public static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
First of all, gateways are for request/reply scenarios; since your client expects no response and the service does not return one, you should be using channel adapters not gateways. Try that and get back if you are still having trouble.
EDIT
#SpringBootApplication
#IntegrationComponentScan
public class So40680673Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(So40680673Application.class, args);
FooGate gate = context.getBean(FooGate.class);
for (int i = 0; i < 10; i++) {
System.out.println(gate.exchange("foo" + i));
}
context.close();
}
#MessagingGateway(defaultRequestChannel = "out.input")
public interface FooGate {
String exchange(String out);
}
#Bean
public IntegrationFlow out(AmqpTemplate amqpTemplate) {
return f -> f.handle(Amqp.outboundGateway(amqpTemplate).routingKey(queue().getName()));
}
#Bean
public IntegrationFlow in(ConnectionFactory connectionFactory) {
return IntegrationFlows.from(Amqp.inboundGateway(connectionFactory, queue().getName()))
.<String, String>transform(String::toUpperCase)
.get();
}
#Bean
public Queue queue() {
return new AnonymousQueue();
}
}

Categories