Jakarta Commonsを使ってJDKクラスを拡張する:パート2はじめにJakarta CommonsはJakartaのさまざまなプロジェクトで使われている再利用可能なクラスの集まりですが、独立したコンポーネントとして自分のJavaプロジェクトの中でも利用できます。本稿はJakarta Commonsのさまざまなコンポーネントを紹介する3回シリーズの第2回です。このシリーズでは実際のサンプルアプリケーションを通じてJakarta Commonsコンポーネントの使い方を説明しています(前回の記事を読むには、ここをクリック)。これらのサンプルは、Jakarta Commonsコンポーネントを例示するだけのものではなく、各自のJavaプロジェクトで再利用できる完全なアプリケーションです。 本稿では次のコンポーネントを取り上げます。
本稿には完全なソースコードが付属しています。ソースコードを実際に試すには、ダウンロードしたzipファイルをローカルドライブに展開し、各サンプルのテストケースをJUnitで実行してください。 著者注
Commonsコンポーネントのアーキテクチャと用法を理解するためには、オブジェクト指向プログラミング(OOP)とGang of Fourのデザインパターン(Command、Decorator、Singleton、Factory、Chain of Responsibility、Composite)についての基本的知識が非常に役立ちます。
Codec Commons Codecには、フォネティックエンコーダ、HexおよびBase64エンコーダ、URLエンコーダといった、一般的なエンコード/デコードアルゴリズムが含まれています。フォネティックエンコーダは言語エンコーダで、検索エンジン、スペルチェック関数、デジタル辞書などのアプリケーションで利用できます。HexおよびBase64エンコーダは、文字を使ってバイナリデータを表現するアプリケーションで利用できます。URLエンコーダはさらにいくつかの機能を持っており、JDKの このコンポーネントには 言語エンコーダ フォネティックアルゴリズムは発音が似ている単語を判別するのに使われます。入力された単語に対して代替案を提示するワープロアプリケーションが格好の例と言えるでしょう。Commons Codecには、 最初のサンプルアプリケーションでは、似通った単語から綴りの誤った単語を判定するために 「words.txt」ファイルには短い単語リストが含まれています。 リスト1 単語データベースの反復的に調べて似通った単語を見つける
ISimilarWordStrategy strategy =
SoundexStrategy.getStrategy(type);
List<String> similarWords = new ArrayList<String>();
// Iterate the words and append similar words to the list
// and return
Iterator<String> 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;
リスト2 アルゴリズムの選択を可能にするインターフェイス
public interface ISimilarWordStrategy { boolean isSimilar(String word1, String word2) throws WordsAssistantException; } リスト3では、 リスト3 Soundexアルゴリズム
private static class SoundexStrategy extends SimilarWordStrategy { public boolean isSimilar(String word1, String word2) throws WordsAssistantException { return soundex.soundex(word1).equals( soundex.soundex(word2)); } } リスト4では、 リスト4 類似性を調べる別の方法
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); } } } バイナリエンコーダバイナリエンコーダはバイナリデータをASCII形式で伝送するのに利用できます。たとえば、XMLに格納されたデジタル名刺にイメージを添付する必要がある場合、バイナリエンコーダはアルゴリズムの1つを使ってイメージのバイナリデータをエンコードし、それを専用のタグでXMLファイルに追加することができます。 「org.apache.commons.codec.binary」パッケージには、 リスト5 WrapItクラスによって生成されるサンプルXML
<data> <meta-data> <entry name=’keywords’ value=’Image, Personal, Face, Profile’/> <entry name=’filename’ value=’test.bmp’/> <entry name=’author’ value=’Narayanan A R’/> </meta-data> <binary> Qk0+QwAAAAAAADYAAAAoAAAASQAAAE4AAAABABgAAAAAAAhDAAAAAAAAAAAAAA エンコードされたバイナリデータは リスト6は リスト6 バイナリコンテンツのエンコードにBase64を使用しているコード
while((bytesRead=inputStream.read(binaryData)) != -1) { if(bytesRead < 1024) { encodedBinaryData = Base64.encodeBase64Chunked( truncateBytes(binaryData, bytesRead)); } else { encodedBinaryData = Base64.encodeBase64Chunked(binaryData); } encodedData.write(encodedBinaryData); } URLエンコーダ
このクラスのサンプルはありません。javadocのURLCodecを見れば容易に理解できるからです。 DBCP DBCP(Database Connection Pool)コンポーネントは、JDBCリソースをプールする必要のあるアプリケーションで利用できます。このコンポーネントは、JDBC接続とは別に JakartaのWebサイトにあるDBCPのドキュメントの説明は包括的で役に立ちます。特にユーザーガイドのDBCP Wikiサイトとシーケンス図は一見の価値があります。「org.apache.commons.dbcp」パッケージのjavadocには、APIに関する詳細な情報も含まれています。DBCPを使用するようにTomcatを設定する方法については、Jakarta-commons Wikiを参照してください。 このセクションのサンプルアプリケーションでは、軽量データベースエンジンHypersonic SQLを使ってDBCPの機能を例示しています。このアプリケーションの主な目的は、名前や電子メールアドレスなどの連絡先情報を格納して取り出すことにあります。このアプリケーションでは、DBCPフレームワークが提供するプリペアードステートメントとプールされたJDBC接続を使用します。ソースファイルはソースコードの「src」フォルダ内のパッケージ「in.co.narayanan.commons.dbcp」に収められています。 このアプリケーションでは以下のクラスを使用しています。
リスト7 DBCPフレームワークを初期化する
// 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は接続とステートメント参照をプールするためにCommons Poolフレームワークを使用します。このフレームワークは汎用性があって、任意のオブジェクトをプールできます。 接続プール参照を リスト8はDAOクラス内のごく一般的なメソッドです。7〜9行目で リスト8 プールされた接続とPreparedStatementオブジェクトの使用
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); } DBCPで使われるプールはカスタマイズ性が高いので、エンタープライズソフトウェアに適しています。 DBUtilsDBUtilsフレームワークはJDBC APIを使いやすくする軽量ラッパーです。これにより、開発者はデータの照会と更新に専念し、リソースのクリーンアップをフレームワークに任せることができます。 このコンポーネントはデータアクセスオブジェクトでの使用にうってつけです。持続性のためにHibernateやJDOを使用する必要はないが、ダイレクトJDBC呼び出しを必要とするアプリケーションでは、DBUtilsを使用すると開発効率が上がります。Jakartaのサイト内の「Overview」と「Examples」は非常に包括的です。このフレームワークに含まれているクラスは多くないので、javadocも読みやすいです。 DBUtilsはどのような働きをするのでしょうか。SQLクエリの このフレームワークには、 以降では、このフレームワークの例を示すために、DBCPフレームワークのサンプルアプリケーションの修正版を紹介します。 リスト9 DBUtilsを使用するためにDatabaseContextクラスに加えられた修正
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; } } リスト10は リスト10 QueryRunnerを使ってレコードを挿入する
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); } } リスト11では、 リスト11 QueryRunnerを使ってデータベースからレコードを選択する
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); } } Commons Emailは、Javaアプリケーションでの電子メールの送信を簡単にするいくつかのクラスから構成されています。Jakartaのサイト内の「Overview」と「Samples」にも有用な情報が含まれています。 このセクションのサンプルには、Commons Emailを使って書かれたコマンドラインツールが含まれています。これは単純なメッセージを送信するツールで、電子メールを手早く送信するのにとても便利です。このツールをさらに拡張すれば、HTML形式のメッセージや添付ファイルもサポートすることもできます。このサンプルアプリケーションでは、クラスパスにJavaMailライブラリとJavaBean Activation Frameworkライブラリを入れておく必要があります(完全なソースはソースコードの「src」フォルダ内のパッケージ「in.co.narayanan.commons.email」に収められています)。 このツールのデザインには以下のクラスが含まれています。
リスト12 単純なメールを送信するためのCLI構文
java EmailBuddy -host mail.mysite.com
-user user
-password password
-to "a@somewhere.org, b@somewhere.org"
-from me@mysite.com
-subject "Commons Mail"
-message "Commons site is really good andリスト13はCommons Email APIの用法を示しています。
リスト13 単純なメールメッセージを送信するためのCommons Emailの使用例(Jakartaのサイトから)
SimpleEmail email = new SimpleEmail(); email.setHostName("mail.myserver.com"); email.addTo("jdoe@somewhere.org", "John Doe"); email.setFrom("me@apache.org", "Me"); email.setSubject("Test message"); email.setMsg("This is a simple test of commons-email"); email.send(); デザインは全体として次のような流れになります。
このツールを拡張して、たとえばHTML形式のメッセージを送信できるようにするには、 i18nJakartaはまだi18nをリリースしていませんが、ソースを入手してビルドするか、スナップショットビルドをダウンロードすることができます。ソースをダウンロードするには、Subversionクライアントが必要です。筆者はSmartSVNを使ってこれを取得しました。ここをクリックすると、クイックスタートガイドを参照できます。 Commons i18nは、スローする例外や表示するエラー/成功メッセージを特定の地域に合わせてローカライズする必要のあるJavaアプリケーションで使用します。メッセージはXMLまたはプロパティファイル形式で格納することができます。XMLの場合、すべてのロケールに関係するメッセージを一緒に格納できるという利点があります。 このセクションのサンプルアプリケーションでは、データベースにアクセスするアプリケーションでロケール固有のエラーをどのようにスローして処理するかを例示しています。筆者はメッセージの格納にXMLを使用しました。Commons i18nライブラリの「commons-i18n-0.3.jar」をクラスパスに入れておく必要があります。また、アプリケーションの起動には 最初のステップでは、システム内のすべてのアプリケーション例外が リスト14 java.lang.ExceptionではなくLocalizedExceptionを拡張するアプリケーション例外
public class DAOException extends LocalizedException { public DAOException( ErrorBundle errorMessage, Throwable throwable) { super(errorMessage, throwable); } public DAOException(ErrorBundle errorMessage) { super(errorMessage); } } 次のステップでは、ロケール固有のエラーメッセージを作成します。リスト15は、このサンプルで使用するXMLファイルです。 リスト15 ロケール固有のエラーメッセージ
<?xml version="1.0" encoding="UTF-8" ?> <messages> <message id="insertionfailed"> <locale language="en"> <entry key="title">Database insertion failed</entry> <entry key="text">Unable to insert row value {0} to the database </entry> <entry key="summary">Summary: Database insertion error</entry> <entry key="details">The given value cannot be inserted すべてのロケールメッセージが同じXMLファイルにまとめられていることに注目してください。Frenchロケールのメッセージには「French」という接頭辞が付いているので、実行時に一目でわかります。 次のステップでは、メッセージをロードするクラスを作成します。これを示しているのがリスト16のコードです。ここでは新しいメッセージプロバイダを追加し、それをキー"dao-errors"に対して格納しています。 リスト16 XMLファイルからメッセージをロードするヘルパークラス
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}); } } 最後のステップでは、作成したアプリケーション例外クラスを使ってシステム内のエラーを示します。リスト17では、 リスト17 ロケール固有のエラーをスローするためにCommons i18nを使用するDOAクラス
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); } } アプリケーションのプレゼンテーションレイヤ内の実際のメッセージを取得したり、それをログに書き込んだりするには、例外オブジェクト内に設定された Commons i18nを使用すると、アプリケーションのデザインが改善されて、グローバル化が容易になります。 まとめJakarta Commonsについて紹介するシリーズ第2回では、次のことを取り上げました。
これからJavaアプリケーションをデザインしたり開発したりするときは、有用なクラスを選んで適切に使用できるのではないでしょうか。このシリーズの第3回でも、引き続き注目すべきコンポーネントについて解説します。 著者紹介Narayanan A.R.(Narayanan A.R.)
テスト駆動開発、アジャイル方法論、Javaテクノロジ、およびデザインパターンの熱烈な擁護者。Javaテクノロジを使ったソフトウェアのデザインおよび開発に携わって数年になる。
関連記事 最新トップニュース
|
|