Unable to pass the JobParameters Value to the tasklet in Spring Batch - spring

I already followed link: Pass JobParameters and ExecutionContext to #Bean Tasklet?, but still facing issue while passing the jobParameters value to the tasklet.
I've developed a code like below:
JobConfiguration.java
#Component
public class JobConfiguration implements ApplicationContextAware{
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private JobExplorer jobExplorer;
#Autowired
private JobRepository jobRepository;
#Autowired
private JobRegistry jobRegistry;
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
#Bean
#StepScope
public Tasklet tasklet(#Value("#{jobParameters['name']}") String name) {
System.out.println("NAME VALUE = "+name);
return (contribution, chunkContext) -> {
System.out.println(String.format("The job run for %s", name));
return RepeatStatus.FINISHED;
};
}
#Bean
public Job job() {
return jobBuilderFactory.get("job")
.start(stepBuilderFactory.get("step1")
.tasklet(tasklet(null))
.build())
.build();
}
}
JobLaunchingController.java
#RestController
public class JobLaunchingController {
#Autowired
private JobLauncher jobLauncher;
#Autowired
private Job job;
#PostMapping("/")
#ResponseStatus(value = HttpStatus.ACCEPTED)
public void launch(#RequestParam("name") String name) throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException, JobParametersInvalidException {
JobParameters jobParameters = new JobParametersBuilder()
.addString("name", name)
.toJobParameters();
JobExecution jobExecution = this.jobLauncher.run(job, jobParameters);
System.out.println("STATUS = "+jobExecution.getStatus());
}
}
Logs:
2018-12-13 23:09:35.930 INFO 20004 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2018-12-13 23:09:35.930 INFO 20004 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2018-12-13 23:09:35.938 INFO 20004 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
2018-12-13 23:09:55.046 INFO 20004 --- [nio-8080-exec-1] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] launched with the following parameters: [{name=foo}]
2018-12-13 23:09:55.414 INFO 20004 --- [nio-8080-exec-1] o.s.batch.core.job.SimpleStepHandler : Executing step: [step1]
The job run for null
2018-12-13 23:09:55.672 INFO 20004 --- [nio-8080-exec-1] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=job]] completed with the following parameters: [{name=foo}] and the following status: [COMPLETED]
STATUS = COMPLETED
StartingAJobApplication.java
#SpringBootApplication
#EnableBatchProcessing
public class StartingAJobApplication {
public static void main(String[] args) {
SpringApplication.run(StartingAJobApplication.class, args);
}
}
CURL:
curl --data 'name=foo' localhost:8080

it's normal because you are passing the tasklet to the job yourself and it has null param.
in order to use #StepScop feature, you need to use the bean spring created
#Bean
public Job job(Tasklet tasklet) {
return jobBuilderFactory.get("job")
.start(stepBuilderFactory.get("step1")
.tasklet(tasklet)
.build())
.build();
}

When you implement “execute” method, you have SetpContribution and ChunkContext as parameters. You have to use ChunkContext to get jobParameter.
#Override
public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
JobParameters jobParameters = chunkContext.getStepContext().getStepExecution().getJobParameters();
...
}

Related

Can not execute controller test with #SpringBootTest

I have a Spring Boot application. Version is 2.3.1.
Main Application looks like:
#AllArgsConstructor
#SpringBootApplication
public class LocalServiceApplication implements CommandLineRunner {
private final DataService dataService;
private final QrReaderServer qrReaderServer;
private final MonitoringService monitoringService;
#Override
public void run(String... args) {
dataService.fetchData();
monitoringService.launchMonitoring();
qrReaderServer.launchServer();
}
public static void main(String[] args) {
SpringApplication.run(LocalServiceApplication.class, args);
}
}
After the application is started I have to execute 3 distinct steps which have done with CommandLineRunner:
first gets remote data and store it locally (for test profile this step is skipped)
start async folder monitoring for file uploads with WatchService.
launch TCP server
I have a controller like:
#Slf4j
#RestController
#AllArgsConstructor
#RequestMapping("/v1/permissions")
public class CarParkController {
private final PermissionService permissionService;
#PostMapping
public CarParkPermission createPermission(#RequestBody #Valid CarParkPermission permission) {
return permissionService.createPermission(permission);
}
}
Ant test with Junit 5 looks like:
#ActiveProfiles("test")
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PermissionService permissionService;
private final Gson gson = new Gson();
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(56)
.permissionCode("1234")
.build();
when(permissionService.createPermission(refEq(permission))).thenReturn(permission);
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(gson.toJson(instance)));
}
}
Looks like it should work fine.
However, the test isn't executed:
2020-08-27 14:42:30.308 INFO 21800 --- [ main] c.s.i.CarParkControllerIntegrationTest : Started CarParkControllerIntegrationTest in 8.593 seconds (JVM running for 10.03)
2020-08-27 14:42:30.334 INFO 21800 --- [ main] c.s.s.s.DataServiceTestImpl : Fetch data for test profile is skipped
2020-08-27 14:42:30.336 DEBUG 21800 --- [ carpark-ex-1] c.s.monitoring.MonitoringServiceImpl : START_MONITORING Results from Cameras for folder: D:\results-from-camera
2020-08-27 14:42:30.751 DEBUG 21800 --- [ main] c.s.netty.TCPServer : TCP Server is STARTED : port 9090
After those lines execution hangs up forever.
UPDATE
Here are details for monitoring task:
#Async
#Override
public void launchMonitoring() {
log.debug("START_MONITORING Results from Cameras for folder: {}", properties.getFolder());
try {
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == ENTRY_CREATE) {
log.info("FILE_CREATED: {}", event.context());
// processing resource
deleteResource(zipFullPath);
} else if (kind == ENTRY_DELETE) {
log.info("RESOURCE_DELETED: {}", event.context());
}
}
key.reset();
}
} catch (InterruptedException e) {
log.error("interrupted exception for monitoring service", e);
Thread.currentThread().interrupt();
}
}
Also AsyncConfiguration is configured with TaskExecutor.
Launch method from TCPServer looks:
#Override
public void launchServer() {
try {
ChannelFuture serverChannelFuture = serverBootstrap.bind(hostAddress).sync();
log.debug("TCP Server is STARTED : port {}", hostAddress.getPort());
serverChannel = serverChannelFuture.channel().closeFuture().sync().channel();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
shutdownQuietly();
}
}
How to solve this issue?
Have understood that execution is blocked (thanks to M.Deinum).
So changed the last method for:
#Async
#Override
public void launchServer() {
// ...
}
And shifted to ObjectMapper instead of Gson for converting instance to JSON format:
#SpringBootTest
#AutoConfigureMockMvc
#ActiveProfiles("test")
class CarParkControllerIntegrationTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private ObjectMapper mapper;
#Test
void testCreatingNewPermissionSuccess() throws Exception {
CarParkPermission permission = CarParkPermission.builder()
.id(444)
.permissionCode("1234")
.build();
postPermission(permission).andExpect(status().isOk());
}
private <T> ResultActions postPermission(T instance) throws Exception {
return this.mockMvc.perform(post("/v1/permissions")
.contentType(MediaType.APPLICATION_JSON)
.content(mapper.writeValueAsString(instance)));
}
}
And finally, it works fine.

Spring batch with quartz configuration not working - Looking for spring metadata table instead of quartz table

I have requirement to develop a spring boot batch using quartz as scheduler.
I have created spring batch application and also configured quartz to trigger job and persist job/trigger data in quartz related RDBMS tables.
Spring boot application starts successfully but when it triggers the job as per cron config. Its throws below error.
2020-04-27 20:42:09.967 INFO 1460 --- [ main] c.b.g.batch.IemsBatchApplication : Started IemsBatchApplication in 12.112 seconds (JVM running for 13.22)
org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [SELECT JOB_INSTANCE_ID, JOB_NAME from BATCH_JOB_INSTANCE where JOB_NAME = ? and JOB_KEY = ?]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00942: table or view does not exist
I am not able to find the root cause that why application is trying to persist data in spring meta data table rather than quartz tables.
Quartz related tables are already created in my db.
Please help me.
Please find below the code for the same.
#Configuration
public class BatchSchedulerConfig {
#Autowired
#Qualifier("batchDataSource")
private DataSource batchDataSource;
#Autowired
JobLauncher jobLauncher;
#Autowired
JobLocator jobLocator;
#Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(BatchJobLauncherConfig.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("jobName", IEMSBatchConstant.MonthlyElectricityMeterReadingJob);
jobDetailFactoryBean.setJobDataAsMap(map);
jobDetailFactoryBean.setGroup("etl_group");
jobDetailFactoryBean.setName("etl_job");
return jobDetailFactoryBean;
}
#Bean
public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setStartDelay(3000);
cronTriggerFactoryBean.setName("cron_trigger");
cronTriggerFactoryBean.setGroup("cron_group");
cronTriggerFactoryBean.setCronExpression("0 0/2 * 1/1 * ? *");
return cronTriggerFactoryBean;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean cronTriggerFactoryBean) throws IOException {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
Map<String, Object> map = new HashMap<String, Object>();
map.put("joblocator", jobLocator);
map.put("JobLauncher", jobLauncher);
scheduler.setSchedulerContextAsMap(map);
scheduler.setTriggers(cronTriggerFactoryBean.getObject());
scheduler.setDataSource(batchDataSource);
scheduler.setQuartzProperties(quartzProperties());
// scheduler.setSchedulerName(schedulerName);
return scheduler;
}
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
}
#Configuration
#EnableConfigurationProperties
public class BatchBaseConfig {
private static final Logger log = LoggerFactory.getLogger(BatchBaseConfig.class);
#Bean
public JobLauncher jobLauncher(JobRepository jobRepository) throws Exception {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository);
return simpleJobLauncher;
}
#Bean
public JobRepository jobRepository(DataSource batchDataSource) throws Exception {
DataSourceTransactionManager batchTransactionManager = new DataSourceTransactionManager();
JobRepositoryFactoryBean jobRepositoryFactoryBean = new JobRepositoryFactoryBean();
batchTransactionManager.setDataSource(batchDataSource);
jobRepositoryFactoryBean.setTransactionManager(batchTransactionManager);
jobRepositoryFactoryBean.setDatabaseType(DatabaseType.ORACLE.getProductName());
//jobRepositoryFactoryBean.setIsolationLevelForCreate("ISOLATION_DEFAULT");
jobRepositoryFactoryBean.setDataSource(batchDataSource);
jobRepositoryFactoryBean.setTablePrefix("QRTZ_");
jobRepositoryFactoryBean.afterPropertiesSet();
return jobRepositoryFactoryBean.getObject();
}
#Bean(name = "batchDataSource")
#Primary
#ConfigurationProperties(prefix = "quartz")
public DataSource batchDataSource() {
DataSource batchDbSrc = DataSourceBuilder.create().build();
return batchDbSrc;
}
#Bean(name = "appDataSource")
#ConfigurationProperties(prefix = "app")
public DataSource appDataSource() {
DataSource appDbSrc = DataSourceBuilder.create().build();
return appDbSrc;
}
#Bean
public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) {
JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor();
jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry);
return jobRegistryBeanPostProcessor;
}
}
#Getter
#Setter
public class BatchJobLauncherConfig extends QuartzJobBean {
private static final Logger log = LoggerFactory.getLogger(BatchJobLauncherConfig.class);
private JobLauncher jobLauncher;
private JobLocator joblocator;
private String jobName;
#Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
JobExecution jobExecution = jobLauncher.run(joblocator.getJob(jobName), new
JobParameters());
log.info("{}_{} was completed successfully", jobExecution.getJobConfigurationName(),
jobExecution.getId());
} catch (JobExecutionAlreadyRunningException | JobRestartException |
JobInstanceAlreadyCompleteException
| JobParametersInvalidException | NoSuchJobException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Configuration
public class JobConfig {
private static final Logger log = LoggerFactory.getLogger(JobConfig.class);
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
private ReadingFromDB readingFromDB;
#Autowired
private ReadingFileWriter readingFileWriter;
#Qualifier(value = "meterReadingJob")
#Bean
public Job meterReadingJob() throws Exception {
log.info("Enter into meterReadingJob method ");
return this.jobBuilderFactory.get("meterReadingJob")
.start(createReadingFile()).build();
}
#Bean
public Step createReadingFile() throws Exception {
return this.stepBuilderFactory.get("createReadingFile").chunk(1)
.reader(readingFromDB).writer(readingFileWriter).build();
}
}
Application.properties
spring.batch.job.enabled=false
spring.main.allow-bean-definition-overriding=true
#Spring's default configuration to generate datasource
app.jdbcUrl=jdbc:oracle:thin:#//<Removed>
app.username=<Removed>
app.password=<Removed>
app.driverClassName=oracle.jdbc.OracleDriver
spring.app.jpa.database-platform=org.hibernate.dialect.Oracle10gDialect
#Quartz dataSource
quartz.jdbcUrl=jdbc:oracle:thin:#//<Removed>
quartz.username=<Removed>
quartz.password=<Removed>
quartz.driverClassName=oracle.jdbc.OracleDriver
Quartz.properties
#scheduler name will be "MyScheduler"
org.quartz.scheduler.instanceName=MyNewScheduler
org.quartz.scheduler.instanceId=AUTO
#maximum of 3 jobs can be run simultaneously
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=50
#Quartz persistent jobStore config
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
#org.quartz.jobStore.dataSource=batchDataSource
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.isClustered=false
Both datasources are getting created and entries are also going for triggers and jobs in datasource names batchDataSource.
Issue comes when job gets triggered and batch tries to insert job details in DB.
Thanks in advance.

Spring Batch Multiple JobExecutionListener - not working

I have a Spring boot batch application.
I have two job and two different JobExecutionListener. But always only one JobExecutionListener is called from both the job.
Job One
#Configuration
public class TestDriveJob {
#Autowired
JobLauncher jobLauncher;
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
TestDriveTasklet testDriveTasklet;
#Autowired
BatchJobMgmt batchJobMgmt;
#Autowired
TestDriveJobExecutionListener testDriveJobExecutionListener;
private static final String JOB_NAME = "test-drive-job";
#Scheduled(fixedDelay = 100 * 1000, initialDelay = 5000)
public void schedule() throws Exception {
if (batchJobMgmt.isJobAllowed(JOB_NAME)) {
JobParameters param = new JobParametersBuilder().addString("JobID", "TEST" + String.valueOf(System.currentTimeMillis())).toJobParameters();
jobLauncher.run(job(), param);
}
}
#Bean
public Job job() {
return jobBuilderFactory.get(JOB_NAME).listener(testDriveJobExecutionListener).incrementer(new RunIdIncrementer()).start(testDriveStep()).build();
}
#Bean
public Step testDriveStep() {
return stepBuilderFactory.get("test-drive-step").tasklet(testDriveTasklet).listener(testDriveJobExecutionListener).build();
}
}
Job Two
#Configuration
public class ExtendedWarrantyJob {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedWarrantyJob.class);
#Autowired
JobLauncher jobLauncher;
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
ExtendedWarrantyTasklet extendedWarrantyTasklet;
#Autowired
BatchJobMgmt batchJobMgmt;
#Autowired
ExtendedWarrantyJobExecutionListener extendedWarrantyJobExecutionListener;
private static final String JOB_NAME = "extended-warranty-job";
#Scheduled(fixedDelay = 900 * 1000, initialDelay = 20000)
public void schedule() throws Exception {
if (batchJobMgmt.isJobAllowed(JOB_NAME)) {
LOGGER.debug("Extended Warranty Job Started at :" + new Date());
JobParameters param = new JobParametersBuilder().addString("JobID", String.valueOf(System.currentTimeMillis())).toJobParameters();
JobExecution execution = jobLauncher.run(job(), param);
LOGGER.debug("Extended Warranty Job finished with status :" + execution.getStatus());
}
}
#Bean
public Job job() {
return jobBuilderFactory.get(JOB_NAME).listener(extendedWarrantyJobExecutionListener).incrementer(new RunIdIncrementer()).start(extendedWarrantyStep())
.build();
}
#Bean
public Step extendedWarrantyStep() {
return stepBuilderFactory.get("extended-warranty-step").tasklet(extendedWarrantyTasklet).build();
}
}
Job one Listener
#Service
public class TestDriveJobExecutionListener implements JobExecutionListener {
private static final Logger LOGGER = LoggerFactory.getLogger(TestDriveJobExecutionListener.class);
#Override
public void beforeJob(JobExecution jobExecution) {
LOGGER.debug("{} Started at : {}", jobExecution.getJobInstance().getJobName(), new Date());
}
#Override
public void afterJob(JobExecution jobExecution) {
LOGGER.debug("{} Completed at : {}", jobExecution.getJobInstance().getJobName(), new Date());
}
}
Job Two Listener
#Service
public class ExtendedWarrantyJobExecutionListener implements JobExecutionListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtendedWarrantyJobExecutionListener.class);
#Override
public void beforeJob(JobExecution jobExecution) {
LOGGER.debug("{} Started=== at : {}", jobExecution.getJobInstance().getJobName(), new Date());
}
#Override
public void afterJob(JobExecution jobExecution) {
LOGGER.debug("{} Completed at : {}", jobExecution.getJobInstance().getJobName(), new Date());
}
}
Both the jobs are executing without any issue. But Always the job one listener is called. Job Two Listener is not working.
I tried adding #Qualifier to the ##Autowired annotation, but it did not make any sense.
any help is appreciated.

Spring batch with Spring boot - configuring JobRepositoryFactoryBean

I'm new to Spring Batch with Boot. I'm having an issue with configuring jobRepositoryFactory bean with postgres as the database.
Below is my configuration class.
#Configuration
#EnableBatchProcessing
#Import(DataSourceConfiguration.class)
public class BatchConfiguration {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Autowired
private PlatformTransactionManager transactionManager;
private final DataSourceConfiguration dataSourceConfiguration;
public BatchConfiguration(DataSourceConfiguration dataSourceConfiguration) {
this.dataSourceConfiguration = dataSourceConfiguration;
}
#Bean
public ElasticReader elasticReader() {
return new ElasticReader();
}
#Bean
public JobRepository jobRepositoryFactoryBean() throws Exception {
JobRepositoryFactoryBean fb = new JobRepositoryFactoryBean();
fb.setDatabaseType("postgres");
fb.setDataSource(dataSourceConfiguration.dataSource());
fb.setTransactionManager(transactionManager);
return fb.getObject();
}
#Bean
public PlatformTransactionManager platformTransactionManager() {
return transactionManager;
}
#Bean
public StageReader stageReader(){
return new StageReader();
}
#Bean
public DocumentItemProcessor processor() {
return new DocumentItemProcessor();
}
#Bean
public ExcelWiter writer() {
return new ExcelWiter();
}
#Bean
public StageWriter stageWriter() {
return new StageWriter();
}
#Bean
public Job importUserJob() {
return jobBuilderFactory.get("importUserJob")
.incrementer(new RunIdIncrementer())
.flow(step1())
.next(step2())
.end()
.build();
}
#Bean
public Step step1() {
return stepBuilderFactory.get("step1")
.<List<JsonObject>,List<JsonObject>> chunk(10)
.reader(stageReader())
.writer(stageWriter())
.build();
}
#Bean
public Step step2() {
return stepBuilderFactory.get("step2")
.<List<OnboardConfigVO>,List<ExportVO>> chunk(10)
.reader(elasticReader())
.processor(processor())
.writer(writer())
.build();
}
}
DataSourceConfiguration.class
#PropertySource("classpath:/batch-postgresql.properties")
public class DataSourceConfiguration {
#Autowired
private Environment environment;
#Autowired
private ResourceLoader resourceLoader;
#PostConstruct
protected void initialize() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(resourceLoader.getResource(environment.getProperty("bach.schema.script")));
populator.setContinueOnError(true);
DatabasePopulatorUtils.execute(populator , dataSource());
}
#Bean(destroyMethod="close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName
(environment.getProperty(batch.jdbc.driver");
dataSource.setUrl(environment.getProperty("batch.jdbc.url"));
dataSource.setUsername(environment.getProperty("batch.jdbc.user"));
dataSource.setPassword(environment.getProperty("batch.jdbc.password"));
return dataSource;
}
Here is the output of Spring boot App run
Using default security password: f5cddd58-4790-427c-83b8-b6c25044db7f
2017-08-09 09:36:59.589 INFO 42576 --- [ main]
o.s.s.web.DefaultSecurityFilterChain : Creating filter chain:
OrRequestMatcher [requestMatchers=[Ant [pattern='/css/**'], Ant
[pattern='/js/**'], Ant [pattern='/images/**'], Ant
[pattern='/webjars/**'], Ant [pattern='/**/favicon.ico'], Ant
[pattern='/error']]], []
2017-08-09 09:36:59.785 INFO 42576 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 9199 (http)
2017-08-09 09:36:59.790 INFO 42576 --- [ main] o.s.b.a.b.JobLauncherCommandLineRunner : Running default command line with: []
2017-08-09 09:36:59.794 INFO 42576 --- [ main] o.s.b.c.r.s.JobRepositoryFactoryBean : No database type set, using meta
data indicating: POSTGRES
2017-08-09 09:36:59.798 INFO 42576 --- [ main]
o.s.b.c.l.support.SimpleJobLauncher : No TaskExecutor has been set,
defaulting to synchronous executor.
I have configured the beans in my configuration class. What am I missing here?
Try the following.
#Autowired
private DataSource dataSource;
And replace fb.setDataSource(dataSourceConfiguration.dataSource()); with fb.setDataSource(dataSource);
As stated by Michael, this is just an INFO message from Spring Boot and it connects to postgres.

Spring Test Service Autowiring resulting in null

I am able to run a Rest Controller PUT method that uses a Autowired #Service as expected via the Spring Boot Application. The same Autowiring is failing while trying to perform a Spring JUnit Test. I have tried reading through multiple threads with similar issues. I made sure I am NOT creating the #Service through the "new" keyword and I tried Context Configuration and other methods .. but all seems to be in vain. I am not sure where I am going wrong.
My Spring Boot Application class -
#SpringBootApplication
#ComponentScan({
"com.initech.myapp.*"
})
public class IngestionServerApplication {
private static final Log logger = LogFactory.getLog(IngestionServerApplication.class);
public static void main(String[] args) {
SpringApplication.run(IngestionServerApplication.class, args);
logger.info("Ingestion Server Application started...");
}
}
Rest Controller Class -
package com.initech.myapp.ingestion.controller;
#RestController
public class IngestionController extends BaseRestController {
private static final Log logger = LogFactory.getLog(IngestionController.class);
// This variable is getting "null" autowiring if invoked
// via Spring Unit Testing framework while it is injected fine via
// Spring Boot app invocation.
#Autowired
public IngestionPayloadProcessor payloadProcessor;
#RequestMapping(path = "/ingest", method = RequestMethod.PUT,
consumes = {
MediaType.APPLICATION_JSON_VALUE,
MediaType.APPLICATION_XML_VALUE
},
produces = {
MediaType.APPLICATION_JSON_VALUE
})
public IngestionSuccessResponse ingest(#RequestHeader(value = "authToken", required = true) String authToken,
#RequestBody String jsonBody) throws Exception {
IngestionPayload ingestionPayload = new IngestionPayload();
ingestionPayload.setAuthToken(authToken);
ingestionPayload.setJsonBody(jsonBody);
IngestionSuccessResponse ingestionSuccessResponse = payloadProcessor.process(ingestionPayload);
return ingestionSuccessResponse;
}
}
Service Class
package com.initech.myapp.ingestion.app.service;
#Service
#ImportResource({"spring.xml"})
public class IngestionPayloadProcessor {
private static final Log logger = LogFactory.getLog(IngestionPayloadProcessor.class);
#Resource(name = "kafkaConfig")
private Properties kafkaConfig;
#Value("${kakfaTopic}")
private String kakfaTopic;
public IngestionSuccessResponse process(IngestionPayload ingestionPayload) throws Exception {
try {
IngestionSuccessResponse ingestionSuccessResponse = buildSuccessResponse(ingestionPayload);
return ingestionSuccessResponse;
}
catch (IllegalStateException e)
{
logger.error("Encountered exception while dropping message in Kafka... " + e.getMessage());
throw e;
}
}
}
private buildSuccessResponse() { ... }
Spring Unit Testing
#RunWith(SpringRunner.class)
#ContextConfiguration(locations = "classpath*:/spring.xml")
#WebMvcTest(IngestionServerApplication.class)
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(
new IngestionServiceController())
.build();
}
#Test
public void testIngestService() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
Error Logs
2016-08-10 19:24:36.500 DEBUG 7505 --- [ main] m.m.a.RequestResponseBodyMethodProcessor : Read [class java.lang.String] as "application/json" with [org.springframework.http.converter.StringHttpMessageConverter#49aa766b]
2016-08-10 19:24:36.510 DEBUG 7505 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Resolving exception from handler [public com.initech.myapp.ingestion.model.IngestionSuccessResponse com.initech.myapp.ingestion.app.controller.myappIngestionServiceController.ingest(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String) throws java.lang.Exception]: java.lang.NullPointerException
2016-08-10 19:24:36.512 DEBUG 7505 --- [ main] .m.m.a.ExceptionHandlerExceptionResolver : Invoking #ExceptionHandler method: public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> com.initech.myapp.base.controller.BaseRestController.handleException(java.lang.Exception,javax.servlet.http.HttpServletRequest)
This is the error handler...
2016-08-10 19:24:36.514 INFO 7505 --- [ main] p.d.i.a.c.myappIngestionServiceController : > handleNoResultException
2016-08-10 19:24:36.574 DEBUG 7505 --- [ main] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Written [{status=500, authToken=6acb1a5c-2ced-4690-95b3-eb7957c7c28a, error=null}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#50d3bf39]
java.lang.AssertionError: Status
Expected :200
Actual :500
Note that I have debugged through the test and I can see that the NullPointer exception is thrown at the below line in the Rest Controller Class as the payloadProcessor object is null.
IngestionSuccessResponse ingestionSuccessResponse = payloadProcessor.process(ingestionPayload);
=====
Unit Test with Mock objects: (This is working as expected)
#RunWith(SpringRunner.class)
#ActiveProfiles("dev")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class IngestionServerUnitTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private IngestionPayloadProcessor processor;
// I was able to get this to work by removing the setUp() method
// that was originally in my code. It was trying to build a new instance
// of the REST controller and then run the "perform" on top of it
// which was causing the test to fail I assume!
/*#Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(
new IngestionServiceController())
.build();
}*/
#Test
public void testIngestService() throws Exception {
IngestionSuccessResponse ingestionSuccessResponse = new IngestionSuccessResponse();
ingestionSuccessResponse.setStatusCode("200");
ingestionSuccessResponse.setRequestId("6acb1a5c-2ced-4690-95b3-eb7957c7c28a");
ingestionSuccessResponse.setReceivedTimestamp("2016-08-09T19:43:30.02234312");
given(this.processor.process(anyObject())).willReturn(ingestionSuccessResponse);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Authorization","5e18685c95b34690");
RequestBuilder requestBuilder = put("/ingest").content("<test>test data</test>").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
When you specify #WebMvcTest there are only certain components of your application that are added to the ApplicationContext. The annotation is actually a composition of a bunch of other annotations as described in the docs: https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/autoconfigure/web/servlet/WebMvcTest.html
Based on this your IngestionPayloadProcessor doesn't get instantiated as a bean, and shouldn't as you are telling the test to only run tests for the web layer. What you need to do is specify a #MockBeanfor the IngestionPayloadProcessor within the test and then define a mock for the method that the controller is calling.
#RunWith(SpringRunner.class)
#WebMvcTest(IngestionServerApplication.class)
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private IngestionPayloadProcessor processor;
#Test
public void testIngestService() throws Exception {
given(this.processor.process(anyObject())).willReturn(new InjestionSuccessResponse());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}
Details on the new features of Spring Boot 1.4 testing are here: https://spring.io/blog/2016/04/15/testing-improvements-in-spring-boot-1-4
* Update based on comments *
Realized you could just auto configure the MockMvc and not need to use the TestRestTemplate. I haven't tested this but it should work.
#RunWith(SpringRunner.class)
#SpringBootTest
#AutoConfigureMockMvc
public class IngestionServerApplicationTests {
#Autowired
private MockMvc mockMvc;
#Test
public void testIngestService() throws Exception {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("authToken","safdafio12312asdfs23");
RequestBuilder requestBuilder = put("/ingest").content("{'testKey' : 'testVal'}").accept(MediaType.APPLICATION_JSON).headers(httpHeaders);
this.mockMvc.perform(requestBuilder).andExpect(status().isOk());
}
}

Resources