メッセージ管理
プログラムの中ではいろいろメッセージを使用することがある。
ほとんどの場合、メッセージの文言はソース埋め込みではなく、何かしら外部化されているものだが、そのやり方はプロジェクトによって様々だ。
また、自分でちょっとしたライブラリを作っていてもメッセージが必要になることがある。
そこで問題になるのが、そのライブラリをどうやってプロジェクトに組み込むかだ。
そのプロジェクトの流儀に従ってメッセージ文字列を取得してもいいのだが、それではそのライブラリがプロジェクト専用になってしまう。
ライブラリの本質はまったくプロジェクトに依存していないのに、メッセージのためだけに依存してしまうのは気に入らない。
そこで思い立ったのが、メッセージリソースとプログラムを仲介するクラス。
作り始めたばかりで実用になるかどうかは疑問だが・・・
/** * メッセージを提供するインターフェイス */ public interface MessageProvider { /** * 指定されたidのメッセージを返す。<br/> * idに対応するメッセージがない場合の挙動は実装次第。 * @param id * @return */ String getMessage(String id); }
import java.util.HashMap; /** * HashMapでメッセージを管理するMessageProvider実装 */ public class SimpleMessageProvider extends HashMap<String, String> implements MessageProvider { /** * 指定されたidのメッセージを返す。<br/> * idに対応するメッセージが無ければnullを返す。 * * @see MessageProvider#getMessage(String) */ @Override public String getMessage(String id) { return get(id); } }
/** * プロパティ形式でメッセージを管理するMessageProvider実装。 */ public class PropertyMessageProvider extends Properties implements MessageProvider { /** * 指定されたidのメッセージを返す。<br/> * idに対応するメッセージが無ければnullを返す。 * * @see MessageProvider#getMessage(String) */ @Override public String getMessage(String id) { return getProperty(id); } }
/** * データベースでメッセージ一覧を管理するMessageProvider実装 */ public class DBMessageProvider implements MessageProvider { /** * メッセージの取得に使用するDB接続のファクトリ。<br/> * {@link javax.sql.DataSource}だとあまりお手軽に実装できないからね・・・ */ public static interface ConnectionFactory { /** * メッセージの取得に使用するDB接続を返す * @return * @throws SQLException */ Connection getConnection() throws SQLException; } private ConnectionFactory factory; private String sql; private boolean initialized; private Map<String, String> messages = new HashMap<String, String>(); /** * DB接続のファクトリとメッセージ一覧を取得するためのsql文を指定する。<br/> * 1つ目のカラムをメッセージID。2つ目のカラムをメッセージ本文として使用する。 * @param factory * @param sql */ public DBMessageProvider(ConnectionFactory factory, String sql) { this.factory = factory; this.sql = sql; } /** * 指定されたidのメッセージを返す。<br/> * idに対応するメッセージが無ければnullを返す。 * * @see MessageProvider#getMessage(String) */ @Override public String getMessage(String id) { if (!initialized) { initialize(); } return messages.get(id); } public void initialize() { // エラーが起きた時に初期化フラグを設定してないと繰り返しエラーが起きることになるので、エラーに関係なく初期化済みとする initialized = true; try { Connection con = factory.getConnection(); try { Statement stmt = con.createStatement(); try { ResultSet rs = stmt.executeQuery(sql); try { if (rs.getMetaData().getColumnCount() < 2) { throw new RuntimeException("invalid sql"); } while (rs.next()) { messages.put(rs.getString(1), rs.getString(2)); } } finally { rs.close(); } } finally { stmt.close(); } } finally { con.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } }
import java.io.InputStream; import java.io.Reader; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; /** * xmlファイルでメッセージを管理するMessageProvider実装。<br/> * <messages><br/> * <message id="msg1">メッセージ1</message><br/> * <message id="msg2">メッセージ2</message><br/> * </messages><br/> * のような形式を想定。<br/> * タグ名やidの属性名は変更可能。 */ public class XmlMessageProvider implements MessageProvider { private InputSource source; private String messageTagPath; private String idName; private boolean initialized; private Map<String, String> messages = new HashMap<String, String>(); /** * ストリームからxmlを読み込む。<br/> * idの属性名は'id'固定。 * @param stream * @param messageTagPath メッセージタグのxpath表記 */ public XmlMessageProvider(InputStream stream, String messageTagPath) { this(new InputSource(stream), messageTagPath); } /** * リーダーからxmlを読み込む。<br/> * idの属性名は'id'固定。 * @param reader * @param messageTagPath メッセージタグのxpath表記 */ public XmlMessageProvider(Reader reader, String messageTagPath) { this(new InputSource(reader), messageTagPath); } /** * ソースからxmlを読み込む。<br/> * idの属性名は'id'固定。 * @param source * @param messageTagPath メッセージタグのxpath表記 */ public XmlMessageProvider(InputSource source, String messageTagPath) { this(source, messageTagPath, "id"); } /** * ソースからxmlを読み込む * @param source * @param messageTagPath メッセージタグのxpath表記 * @param idName id属性名 */ public XmlMessageProvider(InputSource source, String messageTagPath, String idName) { this.source = source; this.messageTagPath = messageTagPath; this.idName = idName; } /** * 指定されたidのメッセージを返す。<br/> * idに対応するメッセージが無ければnullを返す。 * * @see MessageProvider#getMessage(String) */ @Override public String getMessage(String id) { if (!initialized) { initialize(); } return messages.get(id); } private void initialize() { // エラーが起きた時に初期化フラグを設定してないと繰り返しエラーが起きることになるので、エラーに関係なく初期化済みとする initialized = true; try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); Document document = builder.parse(source); XPathFactory xpathFactory = XPathFactory.newInstance(); XPath xpath = xpathFactory.newXPath(); NodeList nodeList = (NodeList) xpath.evaluate(messageTagPath, document.getDocumentElement(), XPathConstants.NODESET); for (int i = 0; i < nodeList.getLength(); i++) { Element e = (Element) nodeList.item(i); messages.put(e.getAttribute(idName), e.getFirstChild().getTextContent()); } } catch (ParserConfigurationException e) { throw new InternalError(e.getMessage()); } catch (Exception e) { throw new RuntimeException(e); } } }