| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| JdbcMessageProvider |
|
| 3.1;3.1 |
| 1 | package org.apache.commons.i18n; | |
| 2 | ||
| 3 | import javax.sql.DataSource; | |
| 4 | import java.util.*; | |
| 5 | import java.sql.*; | |
| 6 | import java.text.MessageFormat; | |
| 7 | ||
| 8 | /** | |
| 9 | * The <code>JdbcMessageProvider</code> provides messages stored in a database (or other data source) | |
| 10 | * accessible via JDBC. The <code>JdbcMessageProvider</code> only has support for different languages, | |
| 11 | * but if support for country or variant is required one could easily subclass it and override the | |
| 12 | * <code>getLocale</code> method. If <code>getLocale</code> is overridden, the languageColumn parameter | |
| 13 | * (or <code>jdbc.sql.locale.column<code> Map entry) of the constructors may be null, since it will not be used. | |
| 14 | * @author Mattias Jiderhamn | |
| 15 | */ | |
| 16 | public class JdbcMessageProvider implements MessageProvider { | |
| 17 | /** | |
| 18 | * This Map has locale or language as key, and a Map with the different | |
| 19 | * messages as value. | |
| 20 | */ | |
| 21 | 5 | private final Map locales = new HashMap(); |
| 22 | ||
| 23 | private String idColumn; | |
| 24 | ||
| 25 | private String languageColumn; | |
| 26 | ||
| 27 | /** | |
| 28 | * Create new JDBC <code>MessageProvider</code> using the provided connection. | |
| 29 | * @param conn The connection to use for initialization. | |
| 30 | * @param table The name of the table holding the messages | |
| 31 | * @param idColumn The name of the column holding the message ID | |
| 32 | * @param languageColumn The name of the column containing the ISO-639 language code. | |
| 33 | * @throws SQLException If there is an error getting data from the table | |
| 34 | */ | |
| 35 | public JdbcMessageProvider(Connection conn, String table, String idColumn, String languageColumn) | |
| 36 | 3 | throws SQLException { |
| 37 | 3 | this.idColumn = idColumn; |
| 38 | 3 | this.languageColumn = languageColumn; |
| 39 | 3 | init(conn, table); |
| 40 | 3 | } |
| 41 | ||
| 42 | /** | |
| 43 | * Create new JDBC <code>MessageProvider</code> using a connection from the provided <code>DataSource</code>. Will | |
| 44 | * get a connection from the <code>DataSource</code>, initialize and then return the connection. | |
| 45 | * @param ds The connection to use for initialization. | |
| 46 | * @param table The name of the table holding the messages | |
| 47 | * @param idColumn The name of the column holding the message ID | |
| 48 | * @param languageColumn The name of the column containing the ISO-639 language code. | |
| 49 | * @throws SQLException If there is an error getting data from the table | |
| 50 | */ | |
| 51 | public JdbcMessageProvider(DataSource ds, String table, String idColumn, String languageColumn) | |
| 52 | 1 | throws SQLException { |
| 53 | 1 | this.idColumn = idColumn; |
| 54 | 1 | this.languageColumn = languageColumn; |
| 55 | 1 | Connection conn = null; |
| 56 | try { | |
| 57 | 1 | conn = ds.getConnection(); |
| 58 | 1 | init(conn, table); |
| 59 | } | |
| 60 | finally { | |
| 61 | 1 | if(conn != null) |
| 62 | 1 | conn.close(); |
| 63 | 0 | } |
| 64 | 1 | } |
| 65 | ||
| 66 | /** | |
| 67 | * Create JDBC <code>MessageProvider</code> from properties in a Map, such | |
| 68 | * as a <code>java.util.Properties</code> object. The following are the properties in use, which | |
| 69 | * are the same as for <code>JDBCResources</code> of Apache Commons Resources | |
| 70 | * jdbc.connect.driver = org.gjt.mm.mysql.Driver | |
| 71 | * jdbc.connect.url = jdbc:mysql://localhost/resources | |
| 72 | * jdbc.connect.login = resourcesTest | |
| 73 | * jdbc.connect.password = resourcesTest | |
| 74 | * | |
| 75 | * jdbc.sql.table = resources | |
| 76 | * jdbc.sql.locale.column = locale | |
| 77 | * jdbc.sql.key.column = msgKey | |
| 78 | */ | |
| 79 | 1 | public JdbcMessageProvider(Map properties) throws ClassNotFoundException, SQLException { |
| 80 | 1 | String driver = (String)properties.get("jdbc.connect.driver"); |
| 81 | 1 | String url = (String)properties.get("jdbc.connect.url"); |
| 82 | 1 | String user = (String)properties.get("jdbc.connect.login"); |
| 83 | 1 | String pass = (String)properties.get("jdbc.connect.password"); |
| 84 | ||
| 85 | 1 | String table = (String)properties.get("jdbc.sql.table"); |
| 86 | 1 | this.idColumn = (String)properties.get("jdbc.sql.key.column"); |
| 87 | 1 | this.languageColumn = (String)properties.get("jdbc.sql.locale.column"); |
| 88 | ||
| 89 | 1 | Class.forName(driver); |
| 90 | 1 | Connection conn = null; |
| 91 | try { | |
| 92 | 1 | conn = DriverManager.getConnection(url, user, pass); |
| 93 | 1 | init(conn, table); |
| 94 | } | |
| 95 | finally { | |
| 96 | 1 | if(conn != null) |
| 97 | 1 | conn.close(); |
| 98 | 0 | } |
| 99 | 1 | } |
| 100 | ||
| 101 | /////////////////////////////////////////////////////////////////////// | |
| 102 | // Methods for initialization | |
| 103 | /////////////////////////////////////////////////////////////////////// | |
| 104 | ||
| 105 | private void init(Connection conn, String table) throws SQLException { | |
| 106 | 5 | Statement stmt = null; |
| 107 | 5 | ResultSet rs = null; |
| 108 | try { | |
| 109 | 5 | stmt = conn.createStatement(); |
| 110 | 5 | rs = stmt.executeQuery("SELECT * FROM " + table); |
| 111 | 5 | String[] valueColumns = getValueColumns(rs); |
| 112 | 15 | while(rs.next()) { |
| 113 | 10 | String id = rs.getString(idColumn); |
| 114 | 10 | Locale locale = getLocale(rs); |
| 115 | 10 | Map entries = new HashMap(); |
| 116 | 30 | for(int i = 0; i < valueColumns.length; i++) { |
| 117 | 20 | String entry = rs.getString(valueColumns[i]); |
| 118 | 20 | if(entry != null) |
| 119 | 20 | entries.put(valueColumns[i], entry); |
| 120 | } | |
| 121 | 10 | Map localeMap = (Map)locales.get(locale); |
| 122 | 10 | if(localeMap == null) { // If first record for this Locale |
| 123 | 10 | localeMap = new HashMap(); |
| 124 | 10 | locales.put(locale, localeMap); |
| 125 | } | |
| 126 | 10 | localeMap.put(id, entries); |
| 127 | 10 | } |
| 128 | } | |
| 129 | finally { | |
| 130 | 5 | if(stmt != null) |
| 131 | 5 | stmt.close(); |
| 132 | 5 | if(rs != null) |
| 133 | 5 | rs.close(); |
| 134 | 0 | } |
| 135 | 5 | } |
| 136 | ||
| 137 | /** | |
| 138 | * Get a String of all the column names, except the ID column and the | |
| 139 | * language column. | |
| 140 | * @param rs A <code>ResultSet</code> ready for reading meta data. | |
| 141 | * @return A String array with the text value column names. | |
| 142 | * @throws SQLException If an SQL error occurs. | |
| 143 | */ | |
| 144 | protected String[] getValueColumns(ResultSet rs) throws SQLException { | |
| 145 | 5 | List output = new LinkedList(); |
| 146 | 5 | ResultSetMetaData metadata = rs.getMetaData(); |
| 147 | 5 | int count = metadata.getColumnCount(); |
| 148 | 25 | for(int i = 0; i < count; i++) { |
| 149 | 20 | String columnName = metadata.getColumnName(i+1); // (Count from 1) |
| 150 | 20 | if(! columnName.equals(idColumn) && ! columnName.equals(languageColumn) ) |
| 151 | 10 | output.add(columnName); |
| 152 | } | |
| 153 | 5 | return (String[])output.toArray(new String[0]); |
| 154 | } | |
| 155 | ||
| 156 | /** | |
| 157 | * Get <code>Locale</code> for the current record in the ResultSet. May be overridden | |
| 158 | * by subclasses to allow for proprietary interpretation of language data. | |
| 159 | * The default implementation assumes the column with the name provided as languageColumn | |
| 160 | * for the constructor contains the ISO-639 code. | |
| 161 | * @return The <code>Locale</code> of the current <code>ResultSet</code> record. | |
| 162 | */ | |
| 163 | protected Locale getLocale(ResultSet rs) throws SQLException { | |
| 164 | 10 | return new Locale(rs.getString(languageColumn).toLowerCase()); |
| 165 | } | |
| 166 | ||
| 167 | /////////////////////////////////////////////////////////////////////// | |
| 168 | // Methods to implement MessageProvider | |
| 169 | /////////////////////////////////////////////////////////////////////// | |
| 170 | ||
| 171 | public String getText(String id, String entry, Locale locale) { | |
| 172 | // TODO: Add Logging | |
| 173 | 18 | Map entries = findEntries(id, locale); |
| 174 | 18 | if(entries != null) { |
| 175 | // TODO: Consider whether we need to recurse up if entries does not contain requested entry | |
| 176 | 17 | return (String)entries.get(entry); |
| 177 | } | |
| 178 | else | |
| 179 | 1 | return null; |
| 180 | } | |
| 181 | ||
| 182 | public Map getEntries(String id, Locale locale) { | |
| 183 | 6 | Map entries = findEntries(id,locale); |
| 184 | 6 | if(entries == null) { // If not found by using specified or default locale |
| 185 | 0 | throw new MessageNotFoundException(MessageFormat.format( |
| 186 | I18nUtils.INTERNAL_MESSAGES.getString(I18nUtils.NO_MESSAGE_ENTRIES_FOUND), | |
| 187 | new String[] { id })); | |
| 188 | } | |
| 189 | 6 | return entries; |
| 190 | } | |
| 191 | ||
| 192 | private Map findEntries(String id, Locale locale) { | |
| 193 | 24 | Map entries = findEntriesRecursively(id,locale); |
| 194 | 24 | if(entries == null) { // If not found by using specified locale, try to use default |
| 195 | 4 | return findEntriesRecursively(id,Locale.getDefault()); |
| 196 | } | |
| 197 | else | |
| 198 | 20 | return entries; |
| 199 | } | |
| 200 | ||
| 201 | /** | |
| 202 | * Find entries by looking at the parent locale (language, country, variant -> | |
| 203 | * language, country -> language) until entry is found. If entry not found for topmost | |
| 204 | * Locale (language only), null is returned. | |
| 205 | */ | |
| 206 | private Map findEntriesRecursively(String id, Locale locale) { | |
| 207 | 37 | Map localeIds = (Map)locales.get(locale); |
| 208 | 37 | if(localeIds != null) { |
| 209 | 25 | Map entries = (Map)localeIds.get(id); |
| 210 | 25 | if(entries != null) |
| 211 | 23 | return entries; |
| 212 | } | |
| 213 | 14 | Locale parentLocale = I18nUtils.getParentLocale(locale); |
| 214 | 14 | if(parentLocale == null) |
| 215 | 5 | return null; |
| 216 | else | |
| 217 | 9 | return findEntriesRecursively(id, parentLocale); // Recursive call |
| 218 | } | |
| 219 | ||
| 220 | } |