JDBCJobStore in Quartz Scheduler

Note: Updated the post to reflect code from Quartz version 2.2.*

As mentioned in the previous post, a Job Store is used by the quartz scheduler to store information about itself, and a JDBCJobStore is a way of maintaining the quartz job details over a database via the JDBC. While the use of RAMJobStore indicates volatile storage of the quartz job details, a JDBCJobStore ensures that the information on the quartz jobs, triggers, calendars etc are available any time in case the system has a downtime and then can be rescheduled once the system is up.

A JDBCJobStore requires some database tables to be present in the data source defined for use of quartz. The sql queries for creating and populating the required tables is available under the docs/dbTables folder of the quartz distribution. The following example uses the MySQL database to schedule a job using the JDBCJobStore.

The file quartz.properties is used by the program to define the quartz properties –
Sample Program – file quartz.properties

org.quartz.scheduler.instanceName = PRINT_SCHEDULER
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 4
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

#specify the jobstore used
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false

#The datasource for the jobstore that is to be used
org.quartz.jobStore.dataSource = myDS

#quartz table prefixes in the database
org.quartz.jobStore.tablePrefix = qrtz_
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.isClustered = true
org.quartz.scheduler.instanceId = PRINT_SCHEDULER

#The details of the datasource specified previously
org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL = jdbc:mysql://localhost:3307/blog_test
org.quartz.dataSource.myDS.user = root
org.quartz.dataSource.myDS.password = root
org.quartz.dataSource.myDS.maxConnections = 20

Sample program PrintScheduler.java

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import ramstore.PrintStatefulJob;

import java.io.*;
import java.util.Properties;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

public class PrintScheduler {

   private Scheduler scheduler;
   public PrintScheduler(String instanceId) {
      try {
         scheduler = new StdSchedulerFactory().getScheduler();
         scheduler.start();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public void schedule() throws SchedulerException {
      JobDetail job = newJob(PrintStatefulJob.class).withIdentity("printjob", "printjobgroup").build();
      job.getJobDataMap().put("count",0);
      Trigger trigger = TriggerBuilder.newTrigger().withIdentity("printTrigger", "printtriggergroup")
            .startNow().withSchedule(simpleSchedule().withIntervalInMilliseconds(100l).repeatForever()).build();
      scheduler.scheduleJob(job, trigger);
   }

   public void stopScheduler() throws SchedulerException {
      scheduler.shutdown();
   }

   public static void main(String[] args) {
      PrintScheduler printScheduler = new PrintScheduler(args[0]);
      try {
//       printScheduler.schedule();
         Thread.sleep(60000l);
         printScheduler.stopScheduler();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

}

PrintStatefulJob.java –

package ramstore;

import org.quartz.*;

@PersistJobDataAfterExecution
public class PrintStatefulJob implements Job{

   @Override
   public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
      JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
      Integer count = jobDataMap.getInt("count");
      count++;
      System.out.println("Printing count : "+count);
      jobDataMap.put("count",count);
   }

}

On successful fire of job, it can be observed that the job details and data blob details are updated in the tables of the database.
The job thus stored in the database can be rescheduled in case there is any need. This post deals with rescheduling of jobs from the JDBCJobStore

Code @ Github – https://github.com/vageeshhoskere/blog/tree/master/quartz

Job Store in Quartz Scheduler – RAMJobStore

Note: Updated the post to reflect code from Quartz version 2.2.*

Job Stores in Quartz scheduler – Job Store is used by the Quartz itself to store the details about the Scheduler, Triggers and Calendar etc. It is important to note that the Job Store instance is not to be used directly but must be left to Quartz itself. The two important JobStore types available are – RAMJobStore and JDBCJobStore

RAMJobStore – As the name indicates using this job store indicates that Quartz store all its scheduler related data in the RAM itself. This method of job store is useful such that, the storing of data in the RAM enables fast retrieval and update of data. But the downside of this method is that it is volatile as the data stored is not permanent and is prone to loss.

This can be specified in the properties file for quartz as –

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

Sample Program – file quartz.properties

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
org.quartz.scheduler.instanceName = PRINT_SCHEDULER
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 2

PrintRamScheduler.java-

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

public class PrintRamScheduler {

   private Scheduler scheduler;
   public PrintRamScheduler() {
      try {
         //create scheduler factory
         scheduler = new StdSchedulerFactory().getScheduler();
         scheduler.start();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   public void schedule() throws SchedulerException {
      //create a job
      JobDetail job = newJob(PrintStatefulJob.class).withIdentity("printjob", "printjobgroup").build();
      //put a count variable that we can keep incrementing
      job.getJobDataMap().put("count",0);
      //create a trigger
      Trigger trigger = TriggerBuilder.newTrigger().withIdentity("printTrigger", "printtriggergroup")
            .startNow().withSchedule(simpleSchedule().withIntervalInMilliseconds(100l).repeatForever()).build();
      //schedule the job
      scheduler.scheduleJob(job, trigger);
   }

   public void stopScheduler() throws SchedulerException {
      //scheduler shutdown
      scheduler.shutdown();
   }

   public static void main(String[] args) {
      PrintRamScheduler printScheduler = new PrintRamScheduler();
      try {
         printScheduler.schedule();
         Thread.sleep(60000l);
         printScheduler.stopScheduler();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

The same can also be implemented without using a properties file but using the following code

Properties prop = new Properties();
prop.setProperty("org.quartz.jobStore.class", "org.quartz.simpl.RAMJobStore");
prop.setProperty("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.setProperty("org.quartz.threadPool.threadCount", "4");
SchedulerFactory schdFact = new StdSchedulerFactory(prop);

PrintStatefulJob.java –


import org.quartz.*;

@PersistJobDataAfterExecution
public class PrintStatefulJob implements Job{

   @Override
   public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
      JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
      Integer count = jobDataMap.getInt("count");
      count++;
      System.out.println("Printing count : "+count);
      jobDataMap.put("count",count);
   }

}

Code @ Github – https://github.com/vageeshhoskere/blog/tree/master/quartz/src/main/java/ramstore

Command Line argument Parsing in Java

Apache commons-cli can be used for parsing command line arguments that might be supplied to the java program. The program below is a sample-program that uses commons-cli’s CommandLineParser utility to parse the arguments.

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;

public class CommandParserUtility {

	public static void main(String[] args) {
		CommandLineParser parser =  new BasicParser();
		Option helpOption = new Option("help",false," Program Help.");
		Option authorOption = new Option("author",false," Program Author.");
		Option userOption = new Option("user",true," Wish User.");
		Options options = new Options();
		options.addOption(authorOption);
		options.addOption(userOption);
		options.addOption(helpOption);
		CommandLine cli = parser.parse(options, args);
		if(cli.hasOption(helpOption.getOpt()))
			System.out.println("CommandParserUtility Usage - \n \nCommandParserUtility -user <User Name> [-author (Show Author Name)][-help|h (Show Usage Help)]\n");
		else if(cli.hasOption(userOption.getOpt()))
			System.out.println("Hey "+cli.getOptionValue(userOption.getOpt())+". You are running the CommandParserUtility.");
		else if(cli.hasOption(authorOption.getOpt()))
			System.out.println("CommandParserUtility authored by Vageesh.");
		else
			System.out.println("CommandParserUtility Usage - \n \nCommandParserUtility -user <User Name> [-author (Show Author Name)][-help|h (Show Usage Help)]\n");
		System.exit(0);
	}

}

In the above program, the command line options available are help, user and author.

Option userOption = new Option("user",true," Wish User.");
Option helpOption = new Option("help",false," Program Help.");

Specifies that the argument is user and the Boolean value true specifies that the option takes value when used as argument and in case of help/author, the Boolean value of false indicates that it does not take any arguments with it.

cli.hasOption(helpOption.getOpt())

This code returns a Boolean indicating whether one of the specified options was passed as argument to the program. If you need a list of all arguments passed to the program that is not from the specified argument list, then the following code can be used which returns a list of all unprocessed arguments

ArrayList<Object> res = cli.getArgList();

Finally the values specified for a particular argument can be accessed using the command –

cli.getOptionValue (“user”);

Data sharing between jobs – Stateful Jobs using Quartz Scheduler

Note: Updated the code to reflect quartz version 2.2.x

The data manipulated by different jobs can be persisted by using the interface StatefulJob. The sample program below shows how stateful jobs work in quartz scheduler.

The updated PrintStatefulJob.java-

package ramstore;

import org.quartz.*;

@PersistJobDataAfterExecution
public class PrintStatefulJob implements Job{

	@Override
	public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
		JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
		Integer count = jobDataMap.getInt("count");
		count++;
		System.out.println("Printing count : "+count);
		jobDataMap.put("count",count);
	}

}

JobListener interface can be used to listen to job execution so as to have customized action on job execution. For example, the program below prints out the incremented count using the PrintJobListener class which implements the JobListener interface-

PrintRamScheduler.java-

package ramstore;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;

public class PrintRamScheduler {

	private Scheduler scheduler;
	public PrintRamScheduler() {
		try {
			//create scheduler factory
			scheduler = new StdSchedulerFactory().getScheduler();
			scheduler.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void schedule() throws SchedulerException {
		JobKey jobKey = JobKey.jobKey("printjob", "printjobgroup");
		//create a job
		JobDetail job = newJob(PrintStatefulJob.class).withIdentity(jobKey).build();
		//put a count variable that we can keep incrementing
		job.getJobDataMap().put("count",0);
		//create a trigger
		Trigger trigger = TriggerBuilder.newTrigger().withIdentity("printTrigger", "printtriggergroup")
				.startNow().withSchedule(simpleSchedule().withIntervalInMilliseconds(100l).repeatForever()).build();
		//schedule the job
		scheduler.scheduleJob(job, trigger);
		//add a listner only for specific job - It is also possible to add a generic listener for all jobs
		scheduler.getListenerManager().addJobListener(new PrintJobListener(), KeyMatcher.keyEquals(jobKey));
	}

	public void stopScheduler() throws SchedulerException {
		//scheduler shutdown
		scheduler.shutdown();
	}

	public static void main(String[] args) {
		PrintRamScheduler printScheduler = new PrintRamScheduler();
		try {
			printScheduler.schedule();
			Thread.sleep(10000l);
			printScheduler.stopScheduler();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

PrintJobListener.java-

package ramstore;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class PrintJobListener implements JobListener {
	@Override
	public String getName() {
		return "PRINT_JOB_LISTENER";
	}

	@Override
	public void jobToBeExecuted(JobExecutionContext jobExecutionContext) {

	}

	@Override
	public void jobExecutionVetoed(JobExecutionContext jobExecutionContext) {
		//called when a job is vetoed via the TriggerListener#vetoJobExecution
		//details - http://www.quartz-scheduler.org/api/2.2.1/org/quartz/TriggerListener.html#vetoJobExecution(org.quartz.Trigger,%20org.quartz.JobExecutionContext)
	}

	@Override
	public void jobWasExecuted(JobExecutionContext jobExecutionContext, JobExecutionException e) {
		System.out.println("Job was executed for count "
				+String.valueOf(jobExecutionContext.getMergedJobDataMap().get("count")));
	}
}

Code @ Git – https://github.com/vageeshhoskere/blog

Using JobDataMap in Quartz Scheduler

The last post was an introduction to scheduling Jobs using Quartz Scheduler. This post is a continuation to it which shells out some more information on using Quartz Scheduler.

At the end of the last post I mentioned that for every run of the scheduled job, a new instance of the job class is created and run, thus making data persistence across different runs of jobs useless with local variables, also inability in passing arguments.

This problem can be overcome by using the JobDataMap. A jobDataMap is actually a MAP which contains key-value pairs of data that can be shared across scheduled jobs in configuring/tracking the job. Below is a sample application which demos the usage of JobDataMap which shares author name.

AlarmJob.java –

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionException;

public class AlarmJob implements Job {

	@Override
	public void execute(JobExecutionContext jeContext) throws JobExecutionException {
		JobDataMap jdMap = jeContext.getJobDetail().getJobDataMap();
		String auth_name = jdMap.get("auth_name").toString();
		System.out.println("WAKE UP CALL "+new Date()+" by "+auth_name);
	}

}

AlarmSchedule.java –

import java.util.Date;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;

public class AlarmSchedule {

	public AlarmSchedule(){
		try{
			SchedulerFactory schdFact = new StdSchedulerFactory();
			Scheduler schd = schdFact.getScheduler();
			schd.start();
			JobDetail jd = new JobDetail("alarmjob", Scheduler.DEFAULT_GROUP, AlarmJob.class);
			jd.getJobDataMap().put("auth_name", "vageesh");
			Trigger t = TriggerUtils.makeDailyTrigger("alarmtrigger", 06, 00);
			t.setStartTime(new Date());
			schd.scheduleJob(jd, t);
		}
		catch(SchedulerException e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new AlarmSchedule();
	}

}

 

Please note that in the above program the jobDataMap is associated with the JobDetail. However, a jobDataMap can also be associated with a trigger and not just jobDetail or even both trigger and jobDetail.

The statement

JobDataMap jdMap = jeContext.getJobDetail().getJobDataMap();

Gets the JobDataMap associated with a jobDetail.
In order to access the JobDataMap associated with a Trigger, the following statement can be used –

JobDataMap jdMap = jeContext.getTrigger().getJobDataMap();

In case jobDataMap associated with both jobDetail and Trigger are used, the jobDataMap accessible in the job instance is actually a merge of the two maps with the values of the latter overriding that of the former. The code below describes the usage in such cases –

JobDataMap jdMap = jeContext.getMergedJobDataMap();

One important point to note here is that any changes that are made to the Map during the job execution are not reflected outside the job. That means any changes to the map made during the job execution is limited to that particular execution of the job and is immediately lost once the execution is complete.

Job Scheduling using Quartz Scheduler

Quartz Scheduler can be used to schedule jobs running periodically. The sample application below uses the Quartz Scheduler to implement an Alarm at 6 AM every morning.

This program includes two files – AlarmJob.java which specifies the job that is to be scheduled and AlarmSchedule.java which includes the scheduler to schedule the job.

AlarmJob.java –

import java.util.Date;

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class AlarmJob implements Job {

	@Override
	public void execute(JobExecutionContext arg0) throws JobExecutionException {
		System.out.println("WAKE UP CALL "+new Date());
	}

}

AlarmSchedule.java –

import java.util.Date;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.TriggerUtils;
import org.quartz.impl.StdSchedulerFactory;

public class AlarmSchedule {

	public AlarmSchedule(){
		try{
			SchedulerFactory schdFact = new StdSchedulerFactory();
			Scheduler schd = schdFact.getScheduler();
			schd.start();
			JobDetail jd = new JobDetail("alarmjob", Scheduler.DEFAULT_GROUP, AlarmJob.class);
			Trigger t = TriggerUtils.makeDailyTrigger("alarmtrigger", 06, 00);
			t.setStartTime(new Date());
			schd.scheduleJob(jd, t);
		}
		catch(SchedulerException e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new AlarmSchedule();
	}

}

 

Please Note:When scheduling the job, the scheduler creates an instance of the job class every time the job is triggered. Therefore using local variables to store values in the Job class is of no use as all the variables are reset during every trigger.

Java Resource Bundle

The resource-bundles in java are useful when dealing with localization of resources that are used in a java program. They contain locale specific resource objects which can be used depending on the target locale. This helps the code to be largely locale independent and also be easily scalable in terms of handling different locales without any changes in the code.

A resource bundle uses a key-value pair where the key is used to localize the resource. For example consider the key-value pair of (SUBMIT,submit) in English as MyResource_en and (SUBMIT, presenter) in French as MyResource_FR. Now, while using the resource bundle, the method getBundle(path-to-resource-bundle, locale) is used to specify the resource bundle that has to be used. So when the localized text for submit is needed, the key SUBMIT can be used and depending on the locale, corresponding string represents the key. That is, SUBMIT is represented as submit or presenter based on the specified locale (English or French respectively) and the getBundle method is used as getBundle(“MyResource”, Locale.ENGLISH) or getBundle(“MyResource”, Locale.FRANCE) respectively.

To create a concrete Resource Bundle one must subclass the ResourceBundle class and implement the handleGetObjects and getKeys method. Java also provides ListResourceBundle and PropertyResourceBundle which is a subclass of ResourceBundle class.

ListResourceBundle:This method uses a java file which extends the ListResourceBundle class. The program given below uses ListResourceBundle.

import java.util.ListResourceBundle;
 
public class MyResource extends ListResourceBundle {
 
        	@Override
        	protected Object[][] getContents() {
                    	return contents;
        	}
        	
        	//Resource Bundle contents in the form of {key, value} pair
static Object[][] contents = {
                    	{ "HELLO_TEXT",
        	  	"Hello everyone!" },
        		{ "GOODBYE_TEXT",
        	  	"Goodbye everyone!" }
        	};
 
}

The program below shows the method for accessing the Resource Bundle MyResource –

import java.util.Locale;
import java.util.ResourceBundle;
 
public class ResourceTest {
 
        	public static void main(String[] args) {
                    	Locale locale = Locale.getDefault();
                    	ResourceBundle res = ResourceBundle.getBundle("MyResource",locale);
                    	System.out.println(res.getString("HELLO_TEXT"));
        	}
 
}
 

Please not that as this is a java file, normal java statements can be used as value for a particular key. For example, {“TEMP_CLASS”, new Temp()} is perfectly acceptable.

PropertyResourceBundle:This method does not use any java file. Instead, it uses a PROPERTIES file (for ex, MyResource.properties). The properties file can contain only text key-value pair and not any java statements. The program given below uses PropertyResourceBundle.

The file MyResource.properties –
HELLO_TEXT=Hello everyone!
GOODBYE_TEXT=Goodbye everyone!

Reading this resource bundle is the same as that for ListResourceBundle.

One point to note in both the cases is that the resource bundle file must be in the classpath of the running program, which if not, results in MissingResourceException.

SAXParser – Parsing XML file

SAX Parser is a part of the JAVA API for XML Processing (JAXP) and is used to parse XML files. SAX parser reads the XML file and sequentially passes it to the application. No information is stored in memory while using the SAX Parser to parse XML.
The first step while using SAXParser is to create an instance of the parser and then provide path to the file that is to be parsed.
SAXParser works on event based model which can be handled using a handler class. A few methods that are available are –
startElement: which is a function that is executed on the start of element parsing event
endElement: which is a function that is executed on the end of element parsing event
characters: which is a Function called to handle the character data inside an element
endDocument: which is a Function that is executed at the end of xml document parsing

Below is a sample program that uses SAXParser to read the xml file –

<?xml version="1.0" encoding="UTF-8"?>
<bookbank>
	<book type="fiction" available="yes">
		<name>Book1</name>
		<author>Author1</author>
		<price>Rs.100</price>
	</book>
	<book type="novel" available="no">
		<name>Book2</name>
		<author>Author2</author>
		<price>Rs.200</price>
	</book>
	<book type="biography" available="yes">
		<name>Book3</name>
		<author>Author3</author>
		<price>Rs.300</price>
	</book>
</bookbank>
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXParserSample extends DefaultHandler {

	private String temp=null;
	private Book book;
	private List<Book> bookBank = new ArrayList<Book>();
	
	public SAXParserSample() throws SAXException,IOException,ParserConfigurationException{
		SAXParserFactory saxParserInstance = SAXParserFactory.newInstance();
		SAXParser saxParser = saxParserInstance.newSAXParser();
		//parse xml books.xml
		saxParser.parse("books.xml", this);
	}
	
	//function which gets called to handle the start of element parsing event
	public void startElement(String uri, String localName, String qName,
		Attributes attributes) throws SAXException {
		//reset temp value
		temp = null;
		if(qName.equalsIgnoreCase("book")) {
			//create a new instance of book
			book = new Book();
			//set value of attributes type and available
			book.setType(attributes.getValue("type"));
			book.setAvailability(attributes.getValue("available"));
		}
	}

	//Function called to handle the character data inside an element
	public void characters(char[] ch, int start, int length) throws SAXException {
		temp = new String(ch,start,length);
	}

	//Function that gets executed to handle the end of an element parsing event
	public void endElement(String uri, String localName,
		String qName) throws SAXException {

		if(qName.equalsIgnoreCase("book")) {
			//add book object to the bookbank list
			bookBank.add(book);
		}else if (qName.equalsIgnoreCase("name")) {
			//element name read complete - add it to object
			book.setName(temp);
		}else if (qName.equalsIgnoreCase("author")) {
			//element author read complete - add it to object
			book.setAuthor(temp);
		}else if (qName.equalsIgnoreCase("price")) {
			//element price read complete - add it to object
			book.setPrice(temp);
		}

	}
	
	public void endDocument(){
		//end of xml reached - print the contents of the entire list
		printBookBank();
	}
	
	public void printBookBank(){
		Iterator<Book> itr = bookBank.iterator();
		while(itr.hasNext()){
			Book b = itr.next();
			System.out.println(b.getBookDetail());
		}
	}
	
	public static void main(String[] args) {
		try{
			new SAXParserSample();
		}
		catch(IOException e){
			e.printStackTrace();
		}
		catch(ParserConfigurationException e){
			e.printStackTrace();
		}
		catch(SAXException e){
			e.printStackTrace();
		}
	}

}

class Book{
	private String type=null;
	private String available=null;
	private String name=null;
	private String author=null;
	private String price=null;
	
	public String getType(){
		return type;
	}
	
	public String getAuthor(){
		return author;
	}
	
	public String getPrice(){
		return price;
	}
	
	public String getAvailability(){
		return available;
	}
	
	public String getName(){
		return name;
	}
	
	public void setName(String name){
		this.name = name;
	}
	
	public void setType(String type){
		this.type = type;
	}
	
	public void setAuthor(String author){
		this.author = author;
	}
	
	public void setPrice(String price){
		this.price = price;
	}
	
	public void setAvailability(String available){
		this.available = available;
	}
	
	public String getBookDetail(){
		return "Name: "+name+"; Author:"+author+"; Price"+price+"; type:"+type+"; availability:"+available;
	}
	
}

On executing the above program you get the following output –
Name: Book1; Author:Author1; PriceRs.100; type:fiction; availability:yes
Name: Book2; Author:Author2; PriceRs.200; type:novel; availability:no
Name: Book3; Author:Author3; PriceRs.300; type:biography; availability:yes

Custom Annotations in Java

The previous post was an introduction to Annotations. This post is on creating custom Annotations. The following code creates an Annotation for description of methods, classes etc


import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target ({ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER,ElementType.CONSTRUCTOR})

public @interface MyAnnotation {
	String value();
}

public class AnnotationTest {

	@MyAnnotation("Constructor for AnnotationTest class")
	public AnnotationTest(){
		
	}
	
	public static void main(String[] args) {
		new AnnotationTest();
	}

}

In the above example, the Interface MyAnnotation, as the name implies is the interface dealing with the description annotation. The first element @Documented signifies that the annotations go to the javadoc

The second option @Retention is used to specify the retention policy for the defined annotations. The three available retention policies are

  1. RetentionPolicy.CLASS – This ensures that the compiler retains the annotations in the class files but discards it during the runtime. (The annotations are not available during runtime).

  2. RetentionPolicy.RUNTIME – The annotations are recorded by the compiler in the class files and the VM retains it during runtime.

  3. RetentionPolicy.SOURCE – The annotations remain only in the source files and not retained either in class files or runtime

The next option @Target is used to specify the target for the current annotation. In the above example the targets are defined as
ElementType.TYPE,ElementType.METHOD,ElementType.PARAMETER,ElementType.CONSTRUCTOR
This means that MyAnnotation can be used for constructors, methods, parameters, class, interface, enums etc.. More details on Target is available here

Finally the interface for MyAnnotation. It contains elements needed for runtime processing. In the above example it contains the element value which is used by the class AnnotationTest where the description string for the constructor that is passed is the value for MyAnnotation

Java Annotations

Annotations are a part of JAVA 5 that provide metadata information related to classes, methods, parameters, Interfaces, Enums etc Annotations can also be included in the Javadoc. Besides, they can be used to provide information to the compiler so as to suppress warnings and also can be used for support tools to examine the code.

Annotations are used with @ symbol in the java source files. There are three predefined Annotations used by the java compiler. They are –

  • @Deprecated

    This annotation indicates that the method must no longer be used. A javadoc can be provided by including a comment block with this annotation which provides the reason for deprecation and the other option that must be used instead.

  • For example –

    /**
     * @deprecated
     * This method is old and deprecated as it does not release memory. Please use newMethod
     * instead of this
    */
    @Deprecated
    public void oldMethod() throws InterruptedException{
    	
    }
    
  • @Override – This annotation informs the compiler that the element is overriding a similar element defined by the super class.
  • For Example –

    
    public class Vehicle{
    	public void horn(){
    		System.out.println("Generic Vehicle blaring its generic horn");
    	}
    }
    
    public class Car extends Vehicle {
    	//Overriding method of the super class Vehicle
    	@Override
    	public void horn(){
    		System.out.println("A Car blaring its horn");
    	}
    	
    }
    
  • @SuppressWarnings

    This annotation indicates to the compiler to suppress any warnings that might have been raised if the method is executed.

  • For example, the compiler generates a warning if it encounters a deprecated element. This annotation is useful in such cases so that such warnings are suppressed.

    
    @SuppressWarnings("deprecation")
    public void oldMethod() throws InterruptedException{
    	
    }