Easy xml parsing using java annotatoins
Java 1.5 is shipped with an annotation API (javax.xml.bind.annotation package) for Xml processing. With this API you don’t have to write code to traverse and parse xml documents. You just need to annotate your business classes with relevant xml tags. Once java classes are properly annotated and a target xml file is provided; the java xml parser populate objects from annotated classes which are injected with relevant xml values.
Here’s a simple example.
A Simple Example
Look at the following xml document that defines a shopping cart with an apple inside.
<shopping-cart name=”myShoppingCart” active=”true”>
<apple price=”12″/>
</shopping-cart>
Listing 1
Let’s try to develop an xml parser for above Document 1 using java annotations. The source code of the ShoppingCart.java and Apple.java classes would be,
@XmlAccessorType (XmlAccessType.FIELD)
@XmlRootElement (name = “test-unit”)
public class ShoppingCart {
@XmlAttribute (name = “name”)
private String name;
@XmlAttribute (name = “active”)
private boolean isActive;
@XmlElementRef
private Apple apple;
…
}
@XmlRootElement (name = “test-case”)
public class Apple {
@XmlAttribute (name = “price”)
private double price;
…
}
Listing 2
Annotations definitions
Followings are the most commonly used annotations for xml processing.
@XmlRootElement
Used at class level to annotate the class with the corresponding xml tag.
@XmlAccessorType
Used at class level. This annotation defines whether to use the field access or the property access to inject the xml elements.
@XmlElement
Used at field / property level. This one defines the mapping xml tag for a property of a java class. This element provides basic primitive data conversions (using String constructors of primitive data types) like String to int, String to boolean etc.
@XmlElements
This element is a container for multiple @XmlElement annotations. Used when a single field on a class is mapped to multiple xml tags.
@XmlElementRef
Defines a dynamic association between a java property and xml element. So the actual xml tag to java field mapping is dynamically derived at the runtime.
Complete java doc is available here http://download.oracle.com/javase/6/docs/api/javax/xml/bind/annotation/package-summary.html.
A complete example
Let’s consider a shopping cart with a lot of fruits.
<shopping-cart name=”myShoppingCart” active=”true”>
<apple price=”12″/>
<orange price=”12″/>
</shopping-cart>
Listing 3
Instead of keeping a single Apple reference inside the shopping cart we would like to keep a reference like List<Fruit> so that we can put any number of Fruits to the shopping cart. So the new source code for shopping cart is,
@XmlAccessorType (XmlAccessType.FIELD)
@XmlRootElement (name = “test-unit”)
public class ShoppingCart {
@XmlAttribute (name = “name”)
private String name;
@XmlAttribute (name = “active”)
private boolean isActive;
@XmlElementRef
private List<Fruit> fruits;
…
}
Listing 4
The implementation of Fruit, Apple and Orange classes are as follows.
//no XmlRoot element is required
public abstract class Fruit {
@XmlAttribute (name = “price”)
private double price;
…
}
@XmlRootElement (name = “apple”)
public class Apple extends Fruit{
…
}
@XmlRootElement (name = “orange”)
public class Orange {
…
}
Listing 5
When the Java xml parser encounters a <shopping-cart> tag, it will populate an instance of the ShoppingCart class. Here we have mapped the name and isActive java properties to the xml tags name and active using @XmlAttribute annotation. So the xml parser will use this mapping to inject values to the ShoppingCart instance.
Next, the xml parser will read the <apple … tag. After a no direct mapping is detected on initial lookup, it will look up for @XmlElementRef elements (Fruit class in this case). It will traverse down the class hierarchy to find a class annotated with “apple” tag (Apple class in this case) and will create an instance of that class and insert it to the Fruits list. (Same for the <orange> tag)
Whitespace collapse
How to implement a correct sax xml parser
Hi folks,
I had been working with xml recently and case across the SAX parser. According to the java doc the “characters” method of the handler may send you discontinues chunks of data. For most of the time this doesn’t happen. But it happens. It happened to me. Depending on the contents of you xml document. So I have implemented a SAX parser with an internal buffer which caches all the incoming data to the characters method and process the whole chunk of data in the endElement method. This method ensures correct behavior of SAX parser. So here’s an example.
public abstract class SampleSAXParser extends HandlerBase {
private StringBuilder recievedData;
public EspmlCardProfileBatchloadHandler(Tracer pTracer,
ReportManager pReportMgr, EspmlElementFieldsValidator validator) {
recievedData = new StringBuilder();// initializing the buffer for input
}
// catch exceptions
@Override
public void startElement(String uri, String name, String qName,
Attributes attrs) throws SAXException {
}
// catch exceptions
/* to do implement BadParameterFormatExceptionRca exceptions */
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
String value = String.valueOf(ch, start, length).trim();
recievedData.append(value); //append input data to buffer
}
private void handleBufferedData() throws Exception {
if (recievedData.length() > 0) {
String value = recievedData.toString();
processRecievedData(value); //process data
recievedData = new StringBuilder(); //create an empty buffer for next read
}
}
// catch exceptions
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
handleBufferedData(); //process element contents
}
}
Hope this helps. Thank you.
Regards,
Lasith.
Java FilenameFilter example – How to search a directory using java
Hi folks,
Following program filters the all the .xml files inside of the “d:/test_folder” directory. Please note that I have provided an inline implementation for the FileNameFilter, which acts as the searching criteria.
File dirFile = new File(“D:/test_folder”);
File[] files = dirFile.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(“.xml”);
}
});
//iterating the sub xml files list
for (File localFile : files) {
//what ever you want to do
}
Hope this helps.
Thank you.
Regards,
Lasith.
Run quartz job at a given set of clock times, every day
Hi folks,
We have an applications which uses quartz for job scheduling. I had to implement this simple behavior. I needed to run a particular job at given set of clock times say (10AM,11.30AM, 1PM, 5PM) every day. I’m familiar with quartz simple and cron triggers. But I found out that either of the triggers are unable to implement this behavior. So I decided to implement a new quartz trigger to support this requirement.(only
)
Here goes the source code.
Source code of the TriggeringTimesCollection class. This is an utility class for the CustomTrigger.
package trigger;
import java.util.*;
import java.text.SimpleDateFormat;
import org.apache.log4j.Logger;
public class TriggeringTimesCollection {
private static final String DATES_DELIMITER = “,”;
public static Logger logger = Logger.getLogger(TriggeringTimesCollection.class);
private List<Date> triggerTimes;
private int currentPoss;
public static TriggeringTimesCollection createInstance(String datesList) {
StringTokenizer st = new StringTokenizer(datesList, DATES_DELIMITER);
List<Date> triggerTimesList = new ArrayList<Date>();
SimpleDateFormat format = new SimpleDateFormat(“HH/mm/ss”);
Date tmpDate;
while (st.hasMoreTokens()) {
try {
tmpDate = format.parse(st.nextToken());
triggerTimesList.add(tmpDate);
} catch (Exception e) {
logger.error(e.getMessage(), e);
continue;
}
}
Collections.sort(triggerTimesList);
return new TriggeringTimesCollection(triggerTimesList, -1);
}
public TriggeringTimesCollection(List<Date> triggerTimes, int currentPoss) {
this.triggerTimes = triggerTimes;
this.currentPoss = currentPoss;
}
public Date getTimeAfter(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
Calendar cal2 = (Calendar) calendar.clone();
Calendar cal3 = (Calendar) calendar.clone();
Date result = null;
cal2.set(Calendar.MILLISECOND, 0);
for (Date tmpDate : triggerTimes) {
cal3.setTime(tmpDate);
cal2.set(Calendar.YEAR, cal3.get(Calendar.YEAR));
cal2.set(Calendar.MONTH, cal3.get(Calendar.MONTH)); //compare regardless of the date
cal2.set(Calendar.DATE, cal3.get(Calendar.DATE));
if (cal2.getTime().before(tmpDate)) {
result = tmpDate;
break;
} }
if (result == null) { // get first start of next date
result = triggerTimes.get(0);
calendar.setTimeInMillis(calendar.getTimeInMillis() + 24 * 60 * 60 * 1000);//switch to next day
}
cal2.setTime(result);
calendar.set(Calendar.HOUR_OF_DAY, cal2.get(Calendar.HOUR_OF_DAY));
calendar.set(Calendar.MINUTE, cal2.get(Calendar.MINUTE));
calendar.set(Calendar.SECOND, cal2.get(Calendar.SECOND));
result = calendar.getTime();
return result;
}
public Date getTimeBefore(Date date) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
Calendar cal2 = (Calendar) calendar.clone();
Calendar cal3 = (Calendar) calendar.clone();
Date result = null;
cal2.set(Calendar.MILLISECOND, 0);
for (Date tmpDate : triggerTimes) {
cal3.setTime(tmpDate);
cal2.set(Calendar.YEAR, cal3.get(Calendar.YEAR));
cal2.set(Calendar.MONTH, cal3.get(Calendar.MONTH)); //compare regardless of the date
cal2.set(Calendar.DATE, cal3.get(Calendar.DATE));
if (cal2.getTime().before(tmpDate)) {
break;
} else { result = tmpDate; } }
if (result == null) { // get first start of prevous date
cal3.setTime(triggerTimes.get(triggerTimes.size() – 1));
cal2.set(Calendar.YEAR, cal3.get(Calendar.YEAR));
cal2.set(Calendar.MONTH, cal3.get(Calendar.MONTH)); //compare regardless of the date
cal2.set(Calendar.DATE, cal3.get(Calendar.DATE));
calendar.setTimeInMillis(calendar.getTimeInMillis() – 24 * 60 * 60 * 1000);//switch to next day
}
cal2.setTime(result);
calendar.set(Calendar.HOUR, cal2.get(Calendar.HOUR)); calendar.set(Calendar.MINUTE, cal2.get(Calendar.MINUTE)); calendar.set(Calendar.SECOND, cal2.get(Calendar.SECOND));
result = calendar.getTime();
return result;
}
}
The source code for the CustomTrigger class.
package trigger;
import org.quartz.*;
import java.util.Date;
import java.util.List;
public class CustomTrigger extends CronTrigger {
private TriggeringTimesCollection triggeringTimesCollection;
public CustomTrigger(String triggerName, String jobName, String triggerDates){
super(triggerName, jobName);
triggeringTimesCollection = TriggeringTimesCollection.createInstance(triggerDates);
}
protected Date getTimeAfter(Date afterTime) {
return (triggeringTimesCollection == null) ? null : triggeringTimesCollection.getTimeAfter(afterTime);
}
protected Date getTimeBefore(Date beforeTime){
return (triggeringTimesCollection == null) ? null : triggeringTimesCollection.getTimeBefore(beforeTime);
}
}
Here CustomTrigger is the actual trigger and TriggeringTimesCollection is an utility class.
Thanks.
Regards,
Lasith.
JMS Exception listeners (How and Why)
Hi folks,
Exception listeners are used to deliver connection related exceptions to the program. It is important to understand that all kind of exceptions will not be delivered to exception listeners. Only the exceptions no where else to throw
.
The implementation is simple. Sub class the javax.jms.ExceptionListener interface.
Implement the onException method.
Register the exception listener to JMS connection object.
Sample code is provided below.
public class JMSExceptionListener implements ExceptionListener {
JMSConnector jmsConnection;
public JMSExceptionListener(JMSConnection jmsConnecion) {
this.jmsConnection = jmsConnection;
}
public void onException(JMSException e)
{ e.printStackTrace();
jmsConnection.restart();
}
}
Here I restart the JMS connection in case of an exception. This is considered as a best practice since most of the exceptions delivered in to the Exception Listeners are connections related Exceptions.
Registering the Exception listener on jms connection.
queueConnection = queueConnectionFactory.createQueueConnection();
queueConnection.setExceptionListener(new JMSExceptionListener(jmsConnection));
queueConnection.start();
Regards,
Lasith.
Windows Batch file exists after maven command completes
Hi folks,
It’s been while since my last post. However here we go again.
Recently I have involved with some java projects. Here we used Apache Maven build tool to build and deploy the project. To avoid the repetition of typing maven build command (a lengthy one with custom build profiles) several times a day, I decided to create the following batch file.
echo OFF
cd “my_project_folder”
mvn clean install
echo ON
pause
Everything went fine with above set of commands excepts the pause command never executes. So I could never have a good look at the maven build results. After some research I found the solution.
mvn itself is a batch file. So with execution of mvn clean install line, my original batch file delegates the control to the maven batch file. But after the maven execution completes it never returns the control to the original batch file. So any command after the mvn clean install line never executes.
In order to return the control to the calling batch file you need to use it as
call mvn clean install
Solved.
Regs,
Lasith.
Hi folks,
Few days ago I was working in a J2EE application developed using EJB 2.0. After completing some development I have deployed the application in JBoss application server and tested it. It worked fine. But when I tried to deploy it on Web Sphere application server, the development failed. The issue was at an entity bean method. I had a getter and a setter for a particular column of the relevent DB table. Let’s say the attribute was Index_No. So my getter and setter was like,
public int getIndex_No();
public void setIndex_No(java.lang.Integer index);
I think you have noticed the issue. Setter sets an Integer object and getter returns an int primitive value. This was quite easy to track. But it was really weired. (Jboss is ok with it) So I learnt that when you working with web sphere, better to write 100% specification compliance source code.
Thanks.
Regads,
Lasith.
This issue is due to a mismatch of jar files. The jboss lib folder $JBOSS_HOME$/server/default/lib/ contains the mail.jar file. (Java Mail library) And as usual you may have included the Java Mail Library jar in your deployed project. (.war or .ear). Both the libraries has the javax.mail.Session class. So the class loader will load the both as two different classes. This originates the issue.
javax.mail.Session session = (javax.mail.Session) context.lookup(“)
Here you going to load the java mail session from a jndi lookup. So the contex.lookup(“..”) returns a javax.mail.Session_JBOSS object. Now you try to cast it. But unfortunately the javax.mail.Session class used in your code is loaded as a different class (javax.mail.Session_USER). So java throws an ClassCastException.
Remove the java mail jar file from the lib folder of your application. It will work fine.
I have experienced the above issue for several other libraries too (log4j).
Hope this will help.
Rgs,
Lasith.
Failed to initialize the ORB Exception
Recently I was working with some EJBs. I use Web Sphere 6.1 as the EJB container. And in the lookup jndi method I encountered an exception “Failed to initialize the ORB Exception”. My application also has a
Jboss version also and it worked fine. So after some searchings I have found that I have to add some VM parameters to do the work. So addition of following parameters solved the problem.
org.omg.CORBA.ORBClass=com.ibm.CORBA.iiop.ORB
org.omg.CORBA.ORBSingletonClass=com.ibm.rmi.corba.ORBSingleton
javax.rmi.CORBA.StubClass=com.ibm.rmi.javax.rmi.CORBA.StubDelegateImpl
javax.rmi.CORBA.PortableRemoteObjectClass=com.ibm.rmi.javax.rmi.PortableRemoteObject
javax.rmi.CORBA.UtilClass=com.ibm.ws.orb.WSUtilDelegateImpl
My IDE was JIdea. When I tried to run the application in JIdea, I have encounterd the same problem gin. I tried adding VM parametrs in Edit Configuration window. But it never worked. I still looking for a solution.
But as a temporary fix, I used the IBM JVM provided with the web sphere installation and it solved the problem for now.