devxlogo

Extend the JDK Classes with Jakarta Commons, Part II

Extend the JDK Classes with Jakarta Commons, Part II

akarta Commons, the set of reusable classes that various Jakarta projects use, are available as separate components, which you can use in your Java projects. This article is the second in a three-part series exploring various Jakarta Commons components and demonstrating them in real-world sample applications. (Click here to read Part I.) The examples don’t only illustrate the Commons components; they are complete, modular applications that highlight the useful features you can reuse in your Java projects.

In particular, this installment explores the following components:

  • Codec
  • DBCP
  • DBUtils
  • Email
  • i18n

The article also includes complete source code. Extract this zip file to a local drive and run it by launching the test cases with JUnit for each of the examples.

Author Note: A basic knowledge of object-oriented programming (OOP), the Gang of Four design patterns (Strategy and Decorator), and a few J2EE patterns (DAO) will be very helpful for understanding the Commons components architecture and the examples presented here.

Codec

Commons Codec contains some general encoding/decoding algorithms, including phonetic encoders, Hex and Base64 encoders, and a URL encoder. The phonetic encoders are language encoders, which are useful in applications such as search engines, spell-check functions, and digital dictionaries. Hex and Base64 encoders are useful in applications that use characters to represent binary data. The URL encoder comes with more features and is considered a replacement for the JDK classes URLEncoder and URLDecoder.

This component also contains the DigestUtils class, which is useful for creating SHA and MD5 digest. The next section shows how to use these classes in real world examples.

Language Encoders

Phonetic algorithms are used to determine words that sound similar. A very good example is a word processing application that suggests alternatives for a typed word. The Commons Codec contains four classes: Soundex, Metaphone, RefinedSoundex, and DoubleMetaphone. Each class uses a separate algorithm to determine whether a word sounds similar to another. Their algorithm descriptions indicate that Metaphone is more accurate than Soundex.

The first example application uses the Soundex class to determine similar words for a misspelled word. It uses the Strategy design pattern to choose among the algorithms, which also enables you to modify the application to support algorithms in the other three classes. (The application classes can be found in the package in.co.narayanan.commons.codec in the src folder of the source code.)

Thewords.txt file contains a small list of words. The Words class abstracts the loading of the word list from the file and adheres to the IWords interface. WordsAssistant is the entry point class for the application. It determines similar words using one of the Soundex algorithms and depends on the IWords interface to access the words. Listing 1 is the implementation for the getSimilarWords method in the WordsAssistant class. It picks a strategy from the SoundexStrategy class and iterates the words to determine a match. It determines the match by calling the isSimilar method in the ISimilarWordStrategy interface. Then it adds the matching words to a list that it returns to the caller.

 Listing 1. Iterating Words Database to Determine Similar Words
ISimilarWordStrategy strategy = SoundexStrategy.getStrategy(type); List similarWords = new ArrayList(); // Iterate the words and append similar words to the list // and return Iterator wordsList = words.getWords().iterator(); String fileWord; while(wordsList.hasNext()) { fileWord = wordsList.next(); try { if(strategy.isSimilar(searchWord, fileWord)) { similarWords.add(fileWord); } } catch (WordsAssistantException e) { throw new WordsAssistantException("Unable to determine similar words", e); } } return similarWords;

The classes SoundexStrategy and CharDiffStrategy provide the implementation to the ISimilarWordStrategy interface and use the Commons Soundex class. Listing 2 is the definition of the ISimilarWordStrategy interface. You can plug new strategies into the sample application by implementing the isSimilar method in this interface.

Listing 2. Interface to Enable Deciding Between the Algorithms
public interface ISimilarWordStrategy { boolean isSimilar(String word1, String word2) throws WordsAssistantException;}

In Listing 3, the method soundex in class org.apache.commons.codec.language.Soundex determines the sound similarity between words. This method returns a code that will be the same for similar words and then compares them to decide whether the words are similar. For instance, the code is A515 for the words ‘compont’, ‘component’, and ‘compenent’.

Listing 3. Soundex Algorithm
private static class SoundexStrategy extends SimilarWordStrategy { public boolean isSimilar(String word1, String word2) throws WordsAssistantException { return soundex.soundex(word1).equals(soundex.soundex(word2)); } }

In Listing 4, the method difference in class org.apache.commons.codec.language.Soundex returns a number between 0 and 4, where 4 is the best match and 0 the worst. This example sets the pivot to 2. The JUnit test case class TestLanguageEncoders invokes the main class method getSimilarWords to demonstrate the application.

Listing 4. Alternate Way to Determine the Similarity
private static class CharDiffStrategy extends SimilarWordStrategy { private static final int DIFF_RANGE = 2; public boolean isSimilar(String word1, String word2) throws WordsAssistantException { try { return (soundex.difference(word1, word2) > DIFF_RANGE) ? true : false; } catch (EncoderException e) { throw new WordsAssistantException("Unable to determine the similarity", e); } } }

Binary Encoders

Binary encoders are useful for transmitting binary data in ASCII form. For instance, if an image needs to be attached to a digital business card stored in XML, a binary encoder can encode the image binary data using one of the algorithms and add it to the XML file in a separate tag.

The package org.apache.commons.codec.binary contains classes Base64, BinaryCodec, and Hex, each representing a unique way of encoding binary data. The sample application demonstrates using the Base64 algorithm to encode a binary file and store it in XML. The XML file contains metadata that describes the data in name/value pair form, which makes it searchable.

The class in.co.narayanan.commons.codec.WrapIt is the only class this example uses. It encodes the binary file and creates XML content along with the metadata details. Listing 5 shows a sample XML generated by this class.

Listing 5. Sample XML Content Generated by WrapIt Class
Qk0+QwAAAAAAADYAAAAoAAAASQAAAE4AAAABABgAAAAAAAhDAAAAAAAAAAAAAADLMjC7MhCLEZB7AWBK8QAa0LAX0HAUUDla6Y7
vLw7vLw7vLw7vHx7vHx7vHx7vHx7vHx7vHx7fHw7fHw7fHw7fHx7fHx7fHx7vHw7vHw7vHw7fLw7fLw7fLw7fHx7fHx7fHxAO7y
8e7y8e7y8e/xPv+WPv+WPv+WPv+WPv+WPv+WPv+WPv+WPv+WPv+WPgA=

The encoded binary data is enclosed in the tag. The metadata is represented as a series of tags. Encoding the binary content and storing it in an XML file simplifies its transmission through the Internet. For instance, a Visa application form in XML sent for processing to the Web service layer can carry the image and other binary contents such as résumés, scanned experience letters, and degree certificates.

Listing 6 is a code snippet from the WrapIt class, which does the actual encoding by reading 1,024 bytes at a time. The code calls the truncateBytes method only once for the last set of data read from the file. The encodeBase64Chunked static method breaks the encoded content into 76-character blocks to make it more human-readable. The metadata allows the XML-formatted data to be searchable.

 Listing 6. Code Snippet Using Base64 for Encoding the Binary Content
while((bytesRead=inputStream.read(binaryData)) != -1) { if(bytesRead

URL Encoder

The class org.apache.commons.codec.net.URLCodec implements the 'www-form-urlencoded' encoding scheme for a string, object, or array of bytes. This class is different from the JDK URLEncoder class for the following reasons:

  • It can perform encoding and decoding for a given character set.
  • In addition to strings, it works for objects and arrays of bytes as well.

No examples are included to illustrate this class usage because the URLCodec javadoc is self-explanatory and very straightforward.

DBCP

The Database Connection Pool (DBCP) component can be used in applications where JDBC resources need to be pooled. Apart from JDBC connections, this provides support for pooling Statement and PreparedStatement instances as well.

The DBCP documentation available on the Jakarta Web site is comprehensive and useful. Particularly the DBCP Wiki site and sequence diagrams presented in the user guide are worth a look. The javadoc of the package org.apache.commons.dbcp contains some detailed information on the API as well. Information about configuring Tomcat to use DBCP can be found here.

The sample application in this section demonstrates the DBCP features by using the lightweight database engine Hypersonic SQL. The primary objective of the application is to store and retrieve contact details such as name and email address. It uses pooled JDBC connections and prepared statements provided by the DBCP framework. The source files can be found in the package in.co.narayanan.commons.dbcp in the src folder of the source code.

The application uses each of the following classes:

  • ContactInfo ? A domain object that represents the details of a contact
  • ContactInfoRepository ? A DAO class that contains methods such as createContactInfo and findByName for creating a contact record and finding a contact, respectively, from the database (This class is provided with a reference to the implementation of IDatabaseContext, from which the pooled connections and statements are retrieved.)
  • DatabaseContext ? Uses DBCP to provide pooled connections and statements to the DAO class

Start the application by running the TestDBCP JUnit test case class. (Be sure to keep hsqldb.jar in the classpath while running the application.) This class initializes the DatabaseContext and passes the reference to ContactInfoRepository to store the details of a contact. Listing 7 shows the code for initializing the DBCP framework.

Listing 7. Initializing the DBCP Framework
// Load the HSQL Database Engine JDBC driver // hsqldb.jar should be in the class path try { Class.forName("org.hsqldb.jdbcDriver"); } catch (ClassNotFoundException e) { throw new DbException("Unable to load the db driver", e); } Properties db = getDBProperties(); ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(db.getProperty("url"), db.getProperty("user"), db.getProperty("password")); connectionPool = new GenericObjectPool(); KeyedObjectPoolFactory stmtPool = new GenericKeyedObjectPoolFactory(null); new PoolableConnectionFactory(connectionFactory,connectionPool,stmtPool,null,false,true); dataSource = new PoolingDataSource(connectionPool);

DBCP uses the Commons Pool framework for pooling connections and statement references. The framework is generic enough to pool any object. During instantiation, the PoolableConnectionFactory class registers itself to the GenericObjectPool instance passed in its constructor. This factory class is used to create new instances of the JDBC connections.

The connection pool reference needs to be passed on to PoolingDataSource. The PoolingDataSource class implements the javax.sql.DataSource interface to provide pooled connections. The dataSource variable is available to the DAO class for fetching a connection.

Passing an instance of the KeyedObjectPoolFactory class to the PoolableConnectionFactory constructor enables pooling the prepared statement objects instances that a JDBC connection creates. The KeyedObjectPoolFactory is a variant of pool factory wherein the resources are pooled per key. For the prepared statements, the query acts as the key. Hence, a pooled prepared statement instance is returned on a call to the same query.

Listing 8 is a very common method in a DAO class. The boldface lines depict fetching a connection from the DatabaseContext and using it to insert a record to the database with a prepared statement.

Listing 8. Usage of the Pooled Connection and PreparedStatement Objects
String query = "INSERT INTO contactinfo(name,email) VALUES(?,?)"; Connection con = null; PreparedStatement createContactPrStmt = null; try { try { con = context.getConnection(); createContactPrStmt = context.getPreparedStatement(con, query); createContactPrStmt.setString(1, contactInfo.getName()); createContactPrStmt.setString(2, contactInfo.getEmailAddress()); createContactPrStmt.execute(); } finally { if(createContactPrStmt != null) { createContactPrStmt.close(); } if(con != null) { con.close(); } } } catch(SQLException e) { throw new DbException("Unable to create ContactInfo record", e); } catch(DbException e) { throw new DbException("Unable to create ContactInfo record", e); }

The pool used by DBCP is very customizable and hence it is suitable for enterprise software.

DBUtils

The DBUtils framework is a thin wrapper around the JDBC API that makes using it easier. It lets a developer concentrate on querying or updating data and leaves the plumbing work of cleaning up resources to the framework.

This component is ideal for use in Data Access Objects. If an application doesn't require Hibernate or JDO for persistence but requires direct JDBC calls, using DBUtils speeds up development. The overview and examples hosted at the Jakarta site are very comprehensive. The javadoc also is easy reading, since the framework doesn't contain many classes.

How does it work? The SQL queries SELECT, INSERT, DELETE, or UPDATE can be passed to the query method of the QueryRunner class. Along with the query, an implementation to the ResultSetHandler interface should be passed. On execution of a SELECT query, the ResultSet object fetched is passed on to the handler. The handler can read the records, create domain objects, and return to the caller. The domain object created will be returned to the caller of the query method.

ArrayHandler, ArrayListHandler, BeanHandler, BeanListHandler, ColumnListHandler, KeyedHandler, MapHandler, MapListHandler, and ScalarHandler are some of the handlers available in the framework. However, you can write your own handler for a specific situation by implementing the ResultSetHandler interface.

The sample application in this section uses a modified version of the DBCP framework sample application to demonstrate this framework. The DatabaseContext and ContactInfoRepository classes are modified to use the DBUtils framework. Listing 9 is a code snippet of these modifications. The code passes the reference of the DataSource instance returned by the DBCP framework to the QueryRunner constructor. Therefore, the instance of the query runner will be available to all DAO classes in the system. (The source files can be found in the package in.co.narayanan.commons.dbutils in the src folder of the source code.)

Listing 9. Changes Done DatabaseContext Class for Using DBUtils
public class DatabaseContext implements IDatabaseContext { // Other declarations private QueryRunner qRunner; public DatabaseContext() throws DbException { init(); } private void init() throws DbException { // Initializing for DBCP dataSource = new PoolingDataSource(connectionPool); qRunner = new QueryRunner(dataSource); } // Other public and private methods public QueryRunner getQueryRunner() { return qRunner; }}

Listing 10 is a code snippet taken from the ContactInfoRepository class to demonstrate using QueryRunner to insert a record into the database. The update method takes the SQL query and its parameters and passes them to the prepared statement. The key point to note here is that the DBUtils framework handles resource cleanup and you need to handle only the SQLException for displaying an error to the user in the GUI.

Listing 10. Using QueryRunner to Insert Records
public void createContactInfo(ContactInfo contactInfo) throws DbException { String query = "INSERT INTO contactinfo(name,email) VALUES(?,?)"; try { context.getQueryRunner().update(query, new Object[] {contactInfo.getName(), contactInfo.getEmailAddress()}); } catch(SQLException e) { throw new DbException("Unable to create ContactInfo record", e); } }

Listing 11 creates an implementation of the ResultSetHandler interface and passes the instance to the query method in QueryRunner, along with the prepared statement parameters for selecting records from the database. It contains a custom ResultSetHandler to demonstrate its usage. An easier approach is to use BeanHandler, which uses reflection to automatically populate the ContactInfo domain object.

Listing 11. Using QueryRunner to Select Records from the Database
private static class ContactInfoRSHandlerByName implements ResultSetHandler { public Object handle(ResultSet results) throws SQLException { return getContactInfo(results); } private ContactInfo getContactInfo(ResultSet results) throws SQLException { if(results.next()) { ContactInfo contact = new ContactInfo(); contact.setName(results.getString("name")); contact.setEmailAddress(results.getString("email")); return contact; } else { return null; } } } public ContactInfo findByName(String name) throws DbException { String query = "select name, email from contactinfo where name=?"; try { return (ContactInfo)context.getQueryRunner().query(query, new Object[] {name}, new ContactInfoRSHandlerByName()); } catch(SQLException e) { throw new DbException("Unable to find ContactInfo record by name", e); } }

Email

Commons Email is composed of a few classes that simplify sending email in a Java application. The overview and samples sections presented at the Jakarta site contain useful additional information.

The example in this section features a command-line tool written with the Commons Email that sends simple messages. I find it very handy for sending quick emails. You can extend this tool further to support HTML-formatted messages and attachments as well. The sample application requires JavaMail libraries and JavaBean Activation Framework libraries in the classpath. (The complete source can be found in the package in.co.narayanan.commons.email in the src folder of the source code.)

The design of the tool includes the following classes:

  • CommandlineParser ? This class parses the arguments passed to the command line for creating an instance of the class SimpleMailCommand, which encapsulates the command arguments and the Commons Email class SimpleEmail. The SimpleMailCommand will be used later by the main class EmailBuddy to send the mail. Listing 12 presents the valid arguments needed to send a simple message.

    Listing 12. CLI Syntax for Sending a Simple Email
    java EmailBuddy -host mail.mysite.com -user user -password password -to "[email protected], [email protected]" -from [email protected] -subject "Commons Mail" -message "Commons site is really good and interesting. You guys have to try it out"

    Listing 13 illustrates Commons Email API usage.

    Listing 13. Sample Commons Email Usage for Sending Simple Mail Message Taken from Jakarta Site
    SimpleEmail email = new SimpleEmail();email.setHostName("mail.myserver.com");email.addTo("[email protected]", "John Doe");email.setFrom("[email protected]", "Me");email.setSubject("Test message");email.setMsg("This is a simple test of commons-email");email.send();
  • SimpleMailCommand ? This class wraps the SimpleEmail class and creates the instance by parsing the command-line arguments.
  • EmailBuddy ? This is the main class responsible for orchestrating the remaining classes.

From start to finish, the design would flow as follows:

  • EmailBuddy delegates the command-line arguments to the CommandlineParser.
  • CommandlineParser uses SimpleMailCommand to parse and populate the SimpleEmail Commons Email class.
  • EmailBuddy fetches the SimpleEmail reference and calls a send method to dispatch the message.

In order to extend this tool--for example, to send a HTML-formatted message--you need to write a class HtmlMailCommand, which parses the command-line arguments and populates the HtmlEmail Commons Email class. Simply modify the CommandlineParser to use HtmlMailCommand.

i18n

Jakarta has not released Commons i18n yet. However, you can either obtain and build the source or download a snapshot build. In order to download the source, you need a Subversion client. I used SmartSVN to get it. Click here for a quick start guide.

You use Commons i18n in any Java application that requires the exceptions thrown and the error or success messages displayed to be localized for a particular region. The messages can be stored in XML or properties file format. In the case of XML, the advantage is that messages pertaining to all locales can be stored together.

The sample application in this section demonstrates how a locale-specific error can be thrown and handled for an application that accesses the database. I used XML for storing the messages. The Commons i18n library commons-i18n-0.3.jar needs to be in the classpath, and you need to run the TestI18n JUnit test case class to launch the application. The source code can be found in the package in.co.narayanan.commons.i18n in the src folder of the source code.

The first step is to make all the application exceptions in the system extend the org.apache.commons.i18n.LocalizedException class instead of java.lang.Exception. Listing 14 shows the application exception class used in the sample application.

Listing 14. Application Exception Extending LocalizedException Instead of java.lang.Exception
public class DAOException extends LocalizedException { public DAOException(ErrorBundle errorMessage, Throwable throwable) { super(errorMessage, throwable); } public DAOException(ErrorBundle errorMessage) { super(errorMessage); }}

The next step is to create the locale-specific error messages. Listing 15 shows the XML file used in this example.

Listing 15. Locale-Specific Error Messages
Database insertion failed Unable to insert row value {0} to the database Summary: Database insertion error The given value cannot be inserted since the database has
reported an error
French - Database insertion failed French - Unable to insert row value {0} to the database French - Summary: Database insertion error French - The given value cannot be inserted since the
database has reported an error

Note that all locale messages are grouped in the same XML file. The messages for the French locale are prefixed with 'French' so that it will be easy to decipher the output during runtime.

The next step is to create a class that will load the messages. The code snippet in Listing 16 illustrates this. It adds a new message provider and stores it against the key "dao-errors".

Listing 16. Helper Class Loading the Messages from the XML File
public class ErrorHelper { static { // Load the message bundle from the XML file MessageManager.addMessageProvider("dao-errors", new XMLMessageProvider(ErrorHelper.class.getResourceAsStream("exceptions.xml"))); } public static ErrorBundle getErrorBundle(String bundleKey, String arg0) { return new ErrorBundle("dao-errors", bundleKey, new Object[] {arg0}); }}

The final step is to use the created application exception class to indicate a failure in the system. In Listing 17, an instance of DAOException is created and passed with a reference to an ErrorBundle instance. The Commons i18n framework fetches messages from MessageManager and sends them to the ErrorBundle class for the given message key. In this case, the message pertaining to key insertionfailed is set.

Listing 17. DAO Class Using Commons i18n for Throwing a Locale-Specific Error
public void createCustomer(String name) throws DAOException { Connection con = null; Statement createStmt = null; try { // Create a customer } catch (SQLException e) { throw new DAOException(ErrorHelper.getErrorBundle("insertionfailed", name), e); } }

To get the actual message in the presentation layer of the application or to write it to the logs, you can access the ErrorBundle set in the exception object.

Using Commons i18n improves the application's design and simplifies globalizing it.

So What Have You Learned?

Now that you've completed this second installment of the Jakarta Commons series, you know the following:

  • The rich features provided by various Commons components
  • Where you can use a particular component or method from a utility class in a real-world Java project
  • A high-level understanding of the packages and classes in various APIs

Now, when you design or develop a Java application, you'll be in a position to pick a useful class and use it appropriately. Part III will discuss some of the notable remaining components.

In Case You Missed It
Extend the JDK Classes with Jakarta Commons, Part I

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist