I have one jar with several jobs, I want to execute only one job each time and retrieve a custom exit code.
For example, I have basic job (retrieveErrorsJob) configuration with one step that will read an input XML file and write the data in specific database table.
Application class
#SpringBootApplication
#EnableBatchProcessing
#Import(CoreCommonsAppComponent.class)
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
private ConfigurationConstants constants;
#Autowired
public Application(ConfigurationConstants constants) {
this.constants = constants;
}
#EventListener(ApplicationStartedEvent.class)
public void idApplication()
{
logger.info("================================================");
logger.info(constants.APPLICATION_NAME() + "-v." + constants.APPLICATION_VERSION() + " started on " + constants.REMOTE_HOST());
logger.info("------------------------------------------------");
}
public static void main(String... args) throws Exception{
ApplicationContext context = SpringApplication.run(Application.class, args);
logger.info("================================================");
SpringApplication.exit(context);
}
}
I can choose one job from command line:
java -jar my-jar.jar --spring.batch.job.names=retrieveErrorsJob --input.xml.file=myfile.xml
Spring Batch starts the correct job.
The problem is that I need the jar to return a custom process exit integer like ExitCode.FAILED == 4 etc. But I always have a ZERO (if ExitCode = SUCCESS or FAILED).
As per the docs, I need to implement ExitCodeMapper interface.
Code (not finished)
public class CustomExitCodeMapper implements ExitCodeMapper {
private static final int NORMAL_END_EXECUTION = 1;
private static final int NORMAL_END_WARNING = 2;
private static final int ABNORMAL_END_WARNING = 3;
private static final int ABNORMAL_END_ERROR = 4;
#Override
public int intValue(String exitCode) {
System.out.println("EXIT CODE = " + exitCode);
switch (exitCode)
{
case "FAILED":
return ABNORMAL_END_WARNING;
default:
return NORMAL_END_EXECUTION;
}
}
}
I can't find a way to use this custom implementation. I could set the custom implementation to CommandLineJobRunner but how to use this class?
Thanks to #Mahendra I've got an idea :)
I've created a JobCompletionNotificationListener class as #Mahendra suggested:
#Component
public class JobCompletionNotificationListener extends JobExecutionListenerSupport {
private static final Logger logger = LoggerFactory.getLogger(JobCompletionNotificationListener.class);
#Override
public void afterJob(JobExecution jobExecution) {
SingletonExitCode exitCode = SingletonExitCode.getInstance();
if(jobExecution.getStatus() == BatchStatus.COMPLETED)
{
logger.info("Exit with code " + ExitCode.NORMAL_END_OF_EXECUTION);
exitCode.setExitCode(ExitCode.NORMAL_END_OF_EXECUTION);
}
else {
logger.info("Exit with code " + ExitCode.ABNORMAL_END_OF_EXECUTION_WARNING);
exitCode.setExitCode(ExitCode.ABNORMAL_END_OF_EXECUTION_WARNING);
}
}
}
But I don't force the application to exit with System.exit() from this class. I've implemented a simple singleton like this:
public class SingletonExitCode {
public ExitCode exitCode = ExitCode.ABNORMAL_END_OF_EXECUTION_WARNING; // Default code 3
private static SingletonExitCode instance = new SingletonExitCode();
private SingletonExitCode() {}
public static SingletonExitCode getInstance() {
return instance;
}
public void setExitCode(ExitCode exitCode) {
this.exitCode = exitCode;
}
}
and I ask the ExitCode from my singleton after closing Spring context:
#SpringBootApplication
#EnableBatchProcessing
#Import(CoreCommonsAppComponent.class)
public class Application {
// a lot of nice things
public static void main(String... args) throws Exception{
ApplicationContext context = SpringApplication.run(Application.class, args);
logger.info("================================================");
SpringApplication.exit(context);
System.exit(SingletonExitCode.getInstance().exitCode.getCode());
}
}
I did this because if we exit directly from JobCompletionNotificationListener class we miss an important line in the logs:
Job: [FlowJob: [name=writeErrorFromFile]] completed with the following parameters: [{-input.xml.file=c:/temp/unit-test-error.xml, -spring.batch.job.names=writeErrorFromFile, run.id=15, input.xml.file=c:/temp/unit-test-error.xml}] and the following status: [FAILED]
And seems that Spring context is not properly closed.
Despite of exit-status of Sprint-Batch's Job (i.e. COMPLETED or FAILED), java process will be completed successfully (and you will get process exit-code as 0).
If you want a custom exit-code for java process so that you can use it any script or somewhere else, you can use JobExecutionListener.
You can check the job's exitStatus in afterJob() and accordingly exit the java process with your desired exit-code (i.e. 4 for FAILURE)
Example of JobExecutionListener
public class InterceptingExitStatus implements JobExecutionListener{
#Override
public void beforeJob(JobExecution jobExecution) {
}
#Override
public void afterJob(JobExecution jobExecution) {
ExitStatus exitStatus = jobExecution.getExitStatus() ;
if(exitStatus == ExitStatus.COMPLETED ){
System.exit(0);
}
if(exitStatus == ExitStatus.FAILED ){
System.exit(4);
}
}
}
and this is how you can configure job-listener in the xml file -
<job id="job">
....
....
<listeners>
<listener ref="interceptingExitStatus "/>
</listeners>
</job>
Spring Boot and Sring Batch already have an internal solution for this, all you need is an extra line of code:
System.exit(SpringApplication.exit(applicationContext));
Here is another example:
public class BatchApplication {
public static void main(String[] args) {
ApplicationContext applicationContext = SpringApplication.run(BatchApplication.class, args);
System.exit(SpringApplication.exit(applicationContext));
}
}
EDIT: If you would like to know how it works check this class: org.springframework.boot.autoconfigure.batch.JobExecutionExitCodeGenerator
Related
I'd like to cache a list of objects that are available for all methods and need it periodically update. I'm wondering if this is safe with multiple threads as per the Spring Boot server. Do I keep the list as static? Or is there a better way to do this?
For example:
#Controller
public class HomeController
{
private static List<String> cachedTerms = new ArrayList<>();
#GetMapping("/getFirstCachedTerm")
public String greeting()
{
if(!cachedTerms.isEmpty())
{
return cachedTerms.get(0);
}else
{
return "no terms";
}
}
//Scheduled to update
private static void updateTerms()
{
//populating from disk IO
cachedTerms.clear();
cachedTerms.add("hello");
}
}
Found out how. By using CopyOnWriteArray, which can be read even while being altered (and thread safe), and by using the #Scheduled tag to automatically run the update.
#Controller
public class HomeController
{
private static final List<String> TERMS_CACHE= new CopyOnWriteArrayList<String>();
#GetMapping("/FirstTerm")
public String getFirstTerm()
{
for(String term: TERMS_CACHE)
{
return term;
}
}
//Scheduled to update
#Scheduled(initialDelay = 1000, fixedRate = 1000)
private static synchronized void updateTerms()
{
//populating from disk IO
TERMS_CACHE.clear();
TERMS_CACHE.add("hello");
}
}
I switched from Apache Commons CLI to Picocli because of the sub command support (and annotation-based declaration).
Consider a command line tool like git, with sub commands like push. Git have a main switch --verbose or -v for enable verbose mode in all sub commands.
How can I implement a main switch that is executed before any sub commands?
This is my test
#CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Callable<Void> {
#Override
public Void call() throws Exception {
System.out.println("#PushCommand.call");
return null;
}
}
#CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp implements Callable<Void> {
#CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
#CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "--verbose", "push");
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
#Override
public Void call() throws Exception {
System.out.println("#GitApp.call");
return null;
}
}
Output is
#PushCommand.call
#GitApp.main after. verbose: true
I would expect, that GitApp.call get called before the sub command get called. But only the sub command get called.
The CommandLine.call (and CommandLine.run) methods only invoke the last subcommand by design, so what you are seeing in the original post is the expected behaviour.
The call and run methods are actually a shortcut. The following two lines are equivalent:
CommandLine.run(callable, args); // internally uses RunLast, equivalent to:
new CommandLine(callable).parseWithHandler(new RunLast(), args);
Update: from picocli 4.0, the above methods are deprecated, and replaced with new CommandLine(myapp).execute(args). The "handler" is now called the "execution strategy" (example below).
There is also a RunAll handler that runs all commands that were matched. The following main method gives the desired behaviour:
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
// before picocli 4.0:
new CommandLine(app).parseWithHandler(new RunAll(), args);
// from picocli 4.0:
//new CommandLine(app).setExecutionStrategy(new RunAll()).execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
}
Output:
#GitApp.call
#PushCommand.call
#GitApp.main after. verbose: true
You may also be interested in the #ParentCommand annotation. This tells picocli to inject an instance of the parent command into a subcommand. Your subcommand can then call methods on the parent command, for example to check whether verbose is true. For example:
Update: from picocli 4.0, use the setExecutionStrategy method to specify RunAll. The below example is updated to use the new picocli 4.0+ API.
import picocli.CommandLine;
import picocli.CommandLine.*;
#Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand implements Runnable {
#ParentCommand // picocli injects the parent instance
private GitApp parentCommand;
public void run() {
System.out.printf("#PushCommand.call: parent.verbose=%s%n",
parentCommand.verboseMode); // use parent instance
}
}
#Command(description = "Version control",
mixinStandardHelpOptions = true, // auto-include --help and --version
subcommands = {PushCommand.class,
HelpCommand.class}) // built-in help subcommand
public class GitApp implements Runnable {
#Option(names = {"-v", "--verbose"},
description = "Verbose mode. Helpful for troubleshooting.")
boolean verboseMode;
public void run() {
System.out.println("#GitApp.call");
}
public static void main(String[] args) {
args = new String[] { "--verbose", "push" };
GitApp app = new GitApp();
int exitCode = new CommandLine(app)
.setExecutionStrategy(new RunAll())
.execute(args);
System.out.println("#GitApp.main after. verbose: " + (app.verboseMode));
System.exit(exitCode);
}
}
Other minor edits: made the annotations a bit more compact by importing the inner classes. You may also like the mixinStandardHelpOptions attribute and the built-in help subcommand that help reduce boilerplate code.
As Picocli supports inheritance with Options I've extracted the --help and --verbose Option into an abstract class BaseCommand and invoke super.call from the subcommands.
abstract class BaseCommand implements Callable<Void> {
#CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display this help message.")
private boolean usageHelpRequested;
#CommandLine.Option(names = {"-v", "--verbose"}, description = "Verbose mode. Helpful for troubleshooting.")
private boolean verboseMode;
#Override
public Void call() throws Exception {
if (verboseMode) {
setVerbose();
}
return null;
}
private void setVerbose() {
System.out.println("enter verbose mode");
}
}
#CommandLine.Command(name = "push",
description = "Update remote refs along with associated objects")
class PushCommand extends BaseCommand {
#Override
public Void call() throws Exception {
super.call();
System.out.println("Execute push command");
return null;
}
}
#CommandLine.Command(description = "Version control", subcommands = {PushCommand.class})
public class GitApp extends BaseCommand {
public static void main(String[] args) {
GitApp app = new GitApp();
CommandLine.call(app, "push", "--verbose");
}
#Override
public Void call() throws Exception {
super.call();
System.out.println("GitApp.call called");
return null;
}
}
I am currently having some trouble with this code:
public class ChatPlugin extends JavaPlugin implements Listener{
private static ChatPlugin instance;
private final static String CHANNEL = "chat";
private JedisPool jedisPool;
private ChatChannel chatChannel;
#Override
public void onEnable()
{
instance = this;
saveDefaultConfig();
this.jedisPool = new JedisPool(new JedisPoolConfig(), getConfig().getString("redis-host"), 6379, 0,getConfig().getString("redis-password"));
this.chatChannel = new ChatChannel();
this.jedisPool.getResource().subscribe(this.chatChannel, new String[]{"chat"});
Bukkit.getPluginManager().registerEvents(this, this);
getCommand("chat").setExecutor(this);
}
#Override
public void onDisable()
{
instance = null;
this.chatChannel.unsubscribe();
this.jedisPool.destroy();
}
#Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (command.getName().equalsIgnoreCase("chat"))
{
getJedis().publish(CHANNEL, args[0]);
sender.sendMessage(ChatColor.GREEN + "Sent!");
}
return true;
}
public Jedis getJedis(){
return jedisPool.getResource();
}
public static ChatPlugin getInstance() {
return instance;
}
public class ChatChannel extends JedisPubSub {
#Override
public void onMessage(String channel, String message) {
new BukkitRunnable(){
#Override
public void run() {
Bukkit.broadcastMessage(message);
}
}.runTask(ChatPlugin.getInstance());
}
}
}
This is my first time working with redis' PUB/SUB feature, and I don't really know what is happening. There are no stacktraces, it's just, when the plugin enables, it freezes. Yes, I know this is because I am doing it on the main thread, but even if I run it async (Which I have) it still does not work.
Hope someone can help!
Thanks
You didn't sent us your saveDefaultConfig method so first of all check out if the plugin config is created correctly. After checking that you don't made any mistakes regarding Bukkit or any logical mistake, try to google for any example or try to find examples on programcreek.com (I've found some for jedis there).
If you still get no clue: try doing what you should have done before you've asked here. Read the wiki of Jedis!
https://github.com/xetorthio/jedis/wiki
I just started using Guice and Play so I guess this is a long but basic question. I checked the guide here: http://eng.42go.com/play-framework-dependency-injection-guice/ but I don't know why my code fails.
First I have a global injector:
public class GlobalInjector {
private static Injector guiceInjector;
private static List<AbstractModule> modules = new ArrayList<AbstractModule>();
public static Injector getInjector() {
return guiceInjector;
}
public static loadModules() {
guiceInjector = Guice.createInjector(modules);
}
public static addModule(AbstractModule module) {
modules.add(module);
}
}
Also I have added Guice to Play by extending the GlobalSettings class (also modified application.global)
public class GuiceExtendedSettings extends GlobalSettings {
#Override
public void onStart(Application app) {
GlobalInjector.loadModules();
}
#Override
public <A> A getControllerInstance(Class<A> controllerClass) {
return GlobalInjector.getInjector().getInstance(controllerClass);
}
}
Then I have my test module acting as a plugin in Play (some required methods are omitted as they do nothing in this piece):
public class TestModule extends AbstractModule implements Plugin {
#Override
public void configure() {
// Worker is a simple class
Worker worker = new SimpleWorker();
MapBinder<String, Worker> mapBinder = MapBinder.newMapBinder(binder(), String.class, Worker.class);
mapBinder.addBinding(worker.getName()).toInstance(worker);
}
#Override
public void onStart() {
GlobalInjector.addModule(this);
}
}
Worker is a simple interface:
public interface Worker {
public String getName();
public String getResult();
}
SimpleWorker:
public class SimpleWorker implements Worker {
public String getName() {
return "SimpleWorker";
}
public String getResult() {
return "works";
}
}
And here is the code piece showing the controller logic: nothing but just print all worker results in the map injected
public class TestController extends Controller {
#Inject
Map<String, Worker> workers;
public Result showWorkers() {
StringBuilder sb = new StringBuilder();
for (Worker worker : workers) {
sb.append(worker.getName() + ": " + worker.getResult() + "</br>");
}
return ok(sb.toString()).as("text/html");
}
}
OK. To make this work, I put the following line in play.plugins:
100:test.TestModule
My idea is:
Play loads the plugin (TestModule) -> TestModule adds itself to the GlobalInjector -> GlobalInjector creates Guice injector -> Guice injects the map to the controller
However the result was
Guice didn't inject the map. The map is still null.
Also how should I test it? (i.e. how can I inject different workers to that map? I hard-coded that part in the above code. But I'm looking for a dynamic way by using different modules.)
public class Test {
#Test
public void testInjector() {
running(fakeApplication(), new Runnable() {
public void run() {
// how can I inject using different modules here?
}
});
}
}
You need to use the fakeApplication helper method that allows you to specify both your global settings object and additional plugins. See http://www.playframework.com/documentation/api/2.1.x/java/play/test/Helpers.html#fakeApplication(java.util.Map,%20java.util.List,%20play.GlobalSettings) for more information.
But basically, your test should look something like:
public class Test {
#Test
public void testInjector() {
Map<String, Object> config = new HashMap<String, Object>();
// add any additional config options, e.g. in-memory db
List<String> plugins = new ArrayList<String>();
plugins.add("full.package.name.TestModule");
GlobalSettings global = null;
try {
global = (GlobalSettings) Class.forName("full.package.name.GuiceExtendedSettings").newInstance();
} catch(Exception e) {}
running(fakeApplication(config, plugins, global), new Runnable() {
public void run() {
// do some assertions
}
});
}
}
You also need to make sure that guice instantiates the test controller or the workers map won't be injected.
Hi this is a test class I have put together before I proceed to my assignment, and I realize it is not working the way I want. Here is my class:
JobLog:
import java.util.PriorityQueue;
public class JobLog {
private PriorityQueue<Job> log;
public JobLog() {
log = new PriorityQueue<Job>();
}
public void addJob(Job newJob) {
log.add(newJob);
}
public void addJob(int clock, int exectionTime, int jobNumber,
int priorityLevel) {
Job newJob = new Job(clock, exectionTime, jobNumber, priorityLevel);
addJob(newJob);
}
public Job getNextJob() {
return log.peek();
}
public Job removeNextJob() {
return log.remove();
}
}
TestClass
public static void main(String[] args) {
// TODO code application logic here
JobLog waitline=new JobLog();
Job Joba=new Job(3,3,4,12);
Job Jobb=new Job(3,3,4,2);
Job Jobc=new Job(3,3,4,3);
Job Jobd=new Job(3,3,4,4);
Job Jobe=new Job(3,3,4,5);
Job Jobf=new Job(3,3,4,5);
Job Jobg=new Job(3,3,4,1);
waitline.addJob(Joba);
waitline.addJob(Jobb);
System.out.println(waitline.getNextJob());
}
I'm getting this result.
assignment3_csc225.Job#1db04ed
I want to return all the information of the job . How?
you have to write a toString method for the job class so that java knows how to display it. What it appears you are getting is the default (Object) toString output.
You can overload the toString() method on the Job class to return the string describing a Job.
Now when you print out a Job, this string will be returned.