Spring Batch - how to fail job when called from SpringApplicationBuilder - spring-boot

I need to be able to have my program exit with an error code so that the scheduler that initiated the program can know that it failed. Currently, I am running my job via SpringApplicationBuilder.
#SpringBootApplication
#EnableBatchProcessing
#Slf4j
public class WeeklyImportApplication extends DefaultBatchConfigurer {
...
public static void main(String[] args) {
handleArguments(args);
new SpringApplicationBuilder(WeeklyImportApplication.class).listeners(new CustomLoggingConfigurationApplicationListener(logConfigurer)).run(args);
finished();
}
#Bean
public Job weeklyImport(JobBuilderFactory jobs, Step determineTableName, Step determineColumnNames, Step readAccessDb) {
return jobs.get("weeklyImport").incrementer(new RunIdIncrementer()).flow(determineTableName).next(determineColumnNames).next(readAccessDb).end().build();
}
#Bean
public Step determineTableName(StepBuilderFactory stepBuilderFactory, ItemReader<String> tableNameReader, TableNameWriter tableNameWriter) {
return stepBuilderFactory.get("determineTableName").<String, String> chunk(100).reader(tableNameReader).writer(tableNameWriter).build();
}
#Bean
public Step determineColumnNames(StepBuilderFactory stepBuilderFactory, ItemReader<String> columnNamesReader, ColumnNamesWriter columnNamesWriter) {
return stepBuilderFactory.get("determineColumnNames").<String, String> chunk(1000).reader(columnNamesReader).writer(columnNamesWriter).build();
}
#Bean
public Step readAccessDb(StepBuilderFactory stepBuilderFactory, ItemReader<WeeklyStoreItem> importReader, ItemWriter<WeeklyStoreItem> weeklyStoreItemWriter, PlatformTransactionManager legacyTransactionManager) {
return stepBuilderFactory.get("readAccessDb").<WeeklyStoreItem, WeeklyStoreItem> chunk(chunkSize).reader(importReader).writer(weeklyStoreItemWriter).transactionManager(legacyTransactionManager).build();
}
...
At any point in the job execution if any step fails, I want to be able to exit out and do two things:
Move the file being processed to specfic folder.
Have the scheduler know via the program exit code that an error occurred.
Right now, the job will exit when an uncaught exception occurs, which is partially what I want, but Spring handles the exception, logs it, and then exits gracefully back to my main method. At that point, I'm not sure how to capture whether the job run was truly successful.

I had the same problem. I managed to solve partially like this (Dave Sayer outlined this idea already in comment).
First I created this created listener:
import org.springframework.batch.core.ExitStatus;
import org.springframework.boot.autoconfigure.batch.JobExecutionEvent;
import org.springframework.context.ApplicationListener;
public class JobResultListener implements
ApplicationListener<JobExecutionEvent> {
private ExitStatus jobExitStatus;
public ExitStatus getJobExitStatus() {
return jobExitStatus;
}
#Override
public void onApplicationEvent(JobExecutionEvent event) {
jobExitStatus = event.getJobExecution().getExitStatus();
}
}
Than I used it to get job execution status this way:
#SpringBootApplication
public class Application{
public static void main(String[] args) {
SpringApplication springApplication =
new SpringApplication(Application.class);
JobResultListener jobResultListener = new JobResultListener();
springApplication.addListeners(jobResultListener);
springApplication.run(args);
if (!ExitStatus.COMPLETED.equals(jobResultListener.getJobExitStatus())) {
throw new IllegalStateException("Job failed");
}
}
}
But for some reason I wasn't able to get instances of exceptions from injected event. But as you mentioned, error is logged into app logs so this mechanism was enough for me to inform called of my process about error with error code != 0.
BTW, I observed that some error penetrated from springApplication.run(args) call, but not sure why not all. Also I didn't find anything mentioned in Spring Boot docs

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

Spring Scheduled should start immediate after application startup

I have a requirement to add schedulers, which will run on a daily basis, but at the same time, I want to run the scheduler on the application startup. But the problem is schedular is not running immediately after application startup.
You can implement the ApplicationRunner interface and execute your business logic in the run method
#Component
public class TaskRun implements ApplicationRunner {
#Override
public void run(ApplicationArguments args) throws Exception {
// do something
}
}
Finally, I solved this by using a listener in the Application.java
#EventListener(ApplicationReadyEvent.class)
public void doSomethingOnceAppIsReady() {
//Calling a schedular method
mySchedular();
}

How to use WatchService (NIO Package) & #Scheduled same time in single project of Spring-Boot?

I have written a script in while I have a schedular class that does something in after every certain time interval and another class in which I am watching a folder continuously for the occurance of any new file. And these both jobs (Schedular + WatchService) has to be endless.
But they are not getting called concurrently.
Called schedular class by - #Scheduled & #ComponentScan(basePackages = "com.project.schedular")
Calling WatchService by - #PostConstruct on method
Already tried Putting #PostConstruct on both and putting both packages in #ComponentScan({"com.project.schedular","com.project.watcher"})
Also tried putting #Async on both the methods.
Main Class:
#SpringBootApplication
#EnableScheduling
#ComponentScan(basePackages = "com.aprstc.schedular")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Component
public class SchedularClass {
#PostConstruct
#Scheduled(fixedRate = 30000)
public void execute() {
//logic of scheduling method
}
Watcher Class:
#Component
public class WaybillReadScript {
#PostConstruct
public void watchFolder() throws InterruptedException, IOException {
System.out.println("Into the watch Folder.");
WatchService watchService = FileSystems.getDefault().newWatchService();
System.out.println(2);
Path path = Paths.get("/home/mypc-630/Work/abc");
System.out.println(3);
try {
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE);
} catch (IOException e) {
e.printStackTrace();
}
WatchKey key;
while ((key = watchService.take()) != null) {
for (WatchEvent<?> event : key.pollEvents()) {
if (event.context().toString().equalsIgnoreCase("wbill.txt"))
processWaybillFile();
}
key.reset();
}
}
}
I expect that both classes must run concurrently.
Watcher Must do continuous watching.
And the scheduler must do a continuous scheduled job.
I think the PostConstruct is the wrong place. PostConstruct is used to initialize your beans/components. And if you make a blocking call with watchService.take(), this PostContruct will never be left and if not all beans are completely created than your application with the scheduler will not start.

How can I run a specific class / utility in a Spring Boot application with wiring?

I have my standard Spring Boot application working. I have situations where I want to run a "job" which is basically some specific method normally run via a user doing something in their browser but I want to run it from command line.
I'm able to run an arbitrary class with gradlew;
./gradlew -PmainClass=kcentral.backingservices.URLMetaExtractor execute
However when run this way none of the "autowiring" works. What is a better way to execute an arbitrary class (that has a main method) such that it also works with any Autowiring?
EDIT:
I got some advice to use a CommandLineRunner and some args, which work to execute the command via:
./gradlew bootRun -Pargs=--reloadTestData
However, the Autowiring of my Repo is failing. What I have is:
#EnableAutoConfiguration
#EnableMongoAuditing
#EnableMongoRepositories(basePackageClasses=KCItemRepo.class)
#ComponentScan(basePackages = {"kcentral"})
public class ReloadTestData implements CommandLineRunner {
#Autowired
AddItemService addItemService;
#Autowired
KCItemRepo itemRepo;
#Autowired
KCItemRatingRepo itemRatingRepo;
private static final Logger log = LoggerFactory.getLogger(ReloadTestData.class);
public void reloadData(){
log.info("reloadData and called");
if (itemRepo == null){
log.error("Repo not found");
return;
}
long c = itemRepo.count();
log.warn("REMOVING ALL items "+c);
itemRepo.deleteAll();
log.warn("REMOVING ALL ratings");
itemRatingRepo.deleteAll();
}
itemRepo is always null even though I wire the same way in my 'regular' spring boot app without an issue. What do I need to do to have it wire properly?
The fact that you say you want to run a "job" suggests that you might want to use a scheduled task within your application, rather than trying to run it through the command line. e.g. Scheduling tasks in Spring
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
If you want to make a command line application work with Autowiring, you can make a command line application by making your Application class implement the CommandLineRunner interface, e.g. Spring Boot Console App
#SpringBootApplication
public class SpringBootConsoleApplication
implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) {
}
}
And add spring.main.web-application-type=NONE to the properties file.
If you want to stop the application after running you can use SpringApplication.exit(ctx). Don't know about your auto-wiring problem though, maybe try printing out the list of available beans which might give some insight. Example:
#Component
public class DoThenQuit implements CommandLineRunner {
#Autowired
private ApplicationContext ctx;
#Override
public void run(String[] args) {
// do some other stuff before quitting
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);
// then quit the application
SpringApplication.exit(ctx);
}
}

Difference between ExitCodeGenerator and System.exit(0)

I recently learned that the proper way to shut down a Spring Boot application is this:
public class Application {
#Bean
public ExitCodeGenerator exitCodeGenerator() {
return new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 0;
}
};
}
public static void main(String[] args) throws Exception {
System.exit(SpringApplication.exit(SpringApplication.run(Application.class, args)));
}
}
This should return an exit code of 0, or whatever I configure it to return in the getExitCode() method. My question is - what is the difference between doing the approach above vs the one below:
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
System.exit(0);
}
}
The application seems to be shut down in exactly the same way by both approaches, at least in the console. So what's the difference?
The ExitCodeGenerator is used if you wish to return a specific exit code when SpringApplication.exit() is called. This exit code can then be passed to System.exit() to return it as a status code.
For example:
#SpringBootApplication
public class ExitCodeApplication {
#Bean
public ExitCodeGenerator exitCodeGenerator() {
return new ExitCodeGenerator() {
#Override
public int getExitCode() {
return 42;
}
};
}
public static void main(String[] args) {
System.exit(SpringApplication
.exit(SpringApplication.run(ExitCodeApplication.class, args)));
}
}
Also, the ExitCodeGenerator interface may be implemented by exceptions. When such an exception is encountered, Spring Boot will return the exit code provided by the implemented getExitCode() method.
Of course for you examples, this does not matter. But what this allows is to "glue" a certain Exception to a certain exit code. Spring, by default, catches all Exceptions that your code might throw - if such an Exception also implements ExitCodeGenerator - that will be used to output the exit code you have provided.
Of course, you can do that mapping by hand, but it's a lot more verbose and harder to maintain; Spring makes this easier. Once that is understood, you might also be interested in ExitCodeExceptionMapper that can map an underlying Exception (with let's say multiple error messages) to different error codes.

Resources