I haven't heard from Daniel Florey for a while now, so my guess is
he is on vacation. Therefore I wonder if there is somebody else
with sandbox commit access that could commit my patch below,
adding a JDBC/database MessageProvider to the i18n component.
I intend to continue working on the remaining issues (as of e-mail
on 2005-06-18) and I would prefer to separate the different issues
into different patches/commits, rather than one big "my
contributions".
If needed for correct encoding/line breaks, I could e-mail a
zipped diff to somebody privately, or create a Bugzilla entry.
Thanks in advance.
Mattias Jiderhamn
Here is the patch:
--------------------------------------------------------
Index: project.properties
===================================================================
--- project.properties (revision 201952)
+++ project.properties (working copy)
@@ -28,3 +28,8 @@
# M A V E N J A R O V E R R I D E
#
---------------------------------------------------------------------
---
maven.jar.override = on
+
+
+# Set target to Java 1.4, since JCoverage does not work with Java
1.5
+maven.compile.source=1.4
+maven.compile.target=1.4
\ No newline at end of file
Index: project.xml
===================================================================
--- project.xml (revision 201952)
+++ project.xml (working copy)
@@ -74,6 +74,23 @@
</contributors>
<dependencies>
+ <!-- Please note, these depenencies are needed for unit
tests only! -->
+ <dependency>
+ <id>hsqldb</id>
+ <version>1.7.3.3</version>
+ </dependency>
+ <dependency>
+ <id>commons-dbcp</id>
+ <version>1.2.1</version>
+ </dependency>
+ <dependency>
+ <id>commons-pool</id>
+ <version>1.2</version>
+ </dependency>
+ <dependency>
+ <id>commons-collections</id>
+ <version>2.1.1</version>
+ </dependency>
</dependencies>
<build>
Index: src/test/org/apache/commons/i18n/I18nUtilsTest.java
===================================================================
--- src/test/org/apache/commons/i18n/I18nUtilsTest.java (revision 0)
+++ src/test/org/apache/commons/i18n/I18nUtilsTest.java (revision 0)
@@ -0,0 +1,45 @@
+/*
+*
+*
====================================================================
+*
+* Copyright 2004 The Apache Software Foundation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
+* See the License for the specific language governing permissions
and
+* limitations under the License.
+*
+*/
+package org.apache.commons.i18n;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtilsTest extends TestCase {
+ public void testGetParentLocale() {
+ assertEquals("Language, country and variant",
+ new Locale("en", "GB"),
+ I18nUtils.getParentLocale(new Locale("en", "GB",
"scottish")));
+
+ assertEquals("Language and country",
+ Locale.ENGLISH,
+ I18nUtils.getParentLocale(new Locale("en", "GB")));
+
+ assertEquals("Language and variant",
+ Locale.ENGLISH,
+ I18nUtils.getParentLocale(new Locale("en", "",
"scottish")));
+
+ assertNull("Language only", I18nUtils.getParentLocale
(Locale.ENGLISH));
+ }
+}
Index: src/test/org/apache/commons/i18n/
ResourceBundleMessageProviderTest.java
===================================================================
--- src/test/org/apache/commons/i18n/
ResourceBundleMessageProviderTest.java (revision 201952)
+++ src/test/org/apache/commons/i18n/
ResourceBundleMessageProviderTest.java (working copy)
@@ -137,7 +137,7 @@
Map germanEntries = new ResourceBundleMessageProvider
("messageBundle").getEntries("helloWorld", Locale.GERMAN);
assertEquals("No of entries", 3, germanEntries.size());
assertEquals("Hallo Welt", germanEntries.get("title"));
- assertEquals("Ich w�nsche Dir alles Gute und ein frohes
Fest!", germanEntries.get("text"));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes
Fest!", germanEntries.get("text"));
assertEquals("This entry is not translated to any other
languages", germanEntries.get("notTranslated"));
Map frenchEntries = new ResourceBundleMessageProvider
("messageBundle").getEntries("helloWorld", Locale.FRENCH);
Index: src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
===================================================================
--- src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
(revision 0)
+++ src/test/org/apache/commons/i18n/JdbcMessageProviderTest.java
(revision 0)
@@ -0,0 +1,169 @@
+package org.apache.commons.i18n;
+
+import junit.framework.TestCase;
+
+import java.sql.DriverManager;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.dbcp.BasicDataSource;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class JdbcMessageProviderTest extends TestCase {
+
+ private static Connection getNewConnection() throws
SQLException {
+ return DriverManager.getConnection("jdbc:hsqldb:.", "sa",
""); // Connect to in-memory database
+ }
+
+ public void setUp() throws Exception {
+ /* Make sure en_US is the default Locale for tests */
+ Locale.setDefault(Locale.US);
+
+ Class.forName("org.hsqldb.jdbcDriver"); // Load HSQLDB
database driver
+ Connection conn = getNewConnection();
+ Statement stmt = conn.createStatement();
+ stmt.execute(
+ "CREATE TABLE messages ( " +
+ " 'id' VARCHAR(30), " +
+ " 'language' VARCHAR(2), " +
+ " 'title' VARCHAR(100), " +
+ " 'text' VARCHAR(100)" +
+ ")");
+ stmt.execute(
+ "INSERT INTO messages VALUES (" +
+ " 'helloWorld', 'en', " +
+ " 'Hello World', 'I wish you a merry christmas!'" +
+ ")"
+ );
+ stmt.execute(
+ "INSERT INTO messages VALUES (" +
+ " 'helloWorld', 'de', " +
+ " 'Hallo Welt', 'Ich wünsche Dir alles Gute und
ein frohes Fest!'" +
+ ")"
+ );
+ stmt.close();
+ conn.close();
+ }
+
+ public void tearDown() throws Exception {
+ Connection conn = getNewConnection();
+ conn.createStatement().execute(
+ "DROP TABLE messages"
+ );
+ conn.close();
+ }
+
+ public void testConstructors() throws Exception {
+ // Connection constructor
+ Connection conn = DriverManager.getConnection
("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+ JdbcMessageProvider jdbcMessageProvider = new
JdbcMessageProvider(conn, "messages", "id", "language");
+ conn.close();
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.ENGLISH));
+
+ // DataSource constructor
+ BasicDataSource dataSource = new BasicDataSource();
+ dataSource.setUrl("jdbc:hsqldb:.");
+ dataSource.setUsername("sa");
+ dataSource.setPassword("");
+ jdbcMessageProvider = new JdbcMessageProvider(dataSource,
"messages", "id", "language");
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.ENGLISH));
+
+ // Map/Properties constructor
+ Properties props = new Properties();
+ props.setProperty("jdbc.connect.driver",
"org.hsqldb.jdbcDriver");
+ props.setProperty("jdbc.connect.url", "jdbc:hsqldb:.");
+ props.setProperty("jdbc.connect.login", "sa");
+ props.setProperty("jdbc.connect.password", "");
+
+ props.setProperty("jdbc.sql.table", "messages");
+ props.setProperty("jdbc.sql.key.column", "id");
+ props.setProperty("jdbc.sql.locale.column", "language");
+ jdbcMessageProvider = new JdbcMessageProvider(props);
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.ENGLISH));
+
+ // Test install
+ MessageManager.addMessageProvider("messages",
jdbcMessageProvider);
+ assertEquals("Hello World", MessageManager.getText
("helloWorld", "title", null, Locale.ENGLISH));
+ }
+
+ public void testGetText() throws Exception {
+ Connection conn = DriverManager.getConnection
("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+ JdbcMessageProvider jdbcMessageProvider = new
JdbcMessageProvider(conn, "messages", "id", "language");
+ conn.close();
+
+ // Explicit default locale
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.ENGLISH));
+ assertEquals("I wish you a merry christmas!",
jdbcMessageProvider.getText("helloWorld", "text", Locale.ENGLISH));
+
+ // Default locale with country
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.US));
+ assertEquals("I wish you a merry christmas!",
jdbcMessageProvider.getText("helloWorld", "text", Locale.US));
+
+ // Default locale with country and variant
+ Locale scottish = new Locale("en", "", "scottish");
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", scottish));
+ assertEquals("I wish you a merry christmas!",
jdbcMessageProvider.getText("helloWorld", "text", scottish));
+
+ assertEquals("Hallo Welt", jdbcMessageProvider.getText
("helloWorld", "title", Locale.GERMAN));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes
Fest!", jdbcMessageProvider.getText("helloWorld", "text",
Locale.GERMAN));
+
+ // Default locale with country
+ assertEquals("Hallo Welt", jdbcMessageProvider.getText
("helloWorld", "title", Locale.GERMANY));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes
Fest!", jdbcMessageProvider.getText("helloWorld", "text",
Locale.GERMANY));
+
+ // Test use of defaule
+ assertEquals("Hello World", jdbcMessageProvider.getText
("helloWorld", "title", Locale.JAPANESE));
+ assertEquals("I wish you a merry christmas!",
jdbcMessageProvider.getText("helloWorld", "text", Locale.JAPANESE));
+
+ // Test non-existent
+ assertNull(jdbcMessageProvider.getText("foo", "bar",
Locale.ENGLISH));
+ }
+
+ public void testGetEntries() throws Exception {
+ Connection conn = DriverManager.getConnection
("jdbc:hsqldb:.", "sa", ""); // Connect to in-memory database
+ JdbcMessageProvider jdbcMessageProvider = new
JdbcMessageProvider(conn, "messages", "id", "language");
+ conn.close();
+
+ // Explicit default locale
+ Map entries = jdbcMessageProvider.getEntries
("helloWorld", Locale.ENGLISH);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String)
entries.get("text"));
+
+ // Default locale with country
+ entries = jdbcMessageProvider.getEntries("helloWorld",
Locale.US);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String)
entries.get("text"));
+
+ // Default locale with country and variant
+ Locale scottish = new Locale("en", "", "scottish");
+ entries = jdbcMessageProvider.getEntries("helloWorld",
scottish);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String)
entries.get("text"));
+
+ entries = jdbcMessageProvider.getEntries("helloWorld",
Locale.GERMAN);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hallo Welt", (String)entries.get("title"));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes
Fest!", (String)entries.get("text"));
+
+ // Default locale with country
+ entries = jdbcMessageProvider.getEntries("helloWorld",
Locale.GERMANY);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hallo Welt", (String)entries.get("title"));
+ assertEquals("Ich wünsche Dir alles Gute und ein frohes
Fest!", (String)entries.get("text"));
+
+ // Test use of defaule
+ entries = jdbcMessageProvider.getEntries("helloWorld",
Locale.JAPANESE);
+ assertEquals("No of entries", 2, entries.size());
+ assertEquals("Hello World", (String)entries.get("title"));
+ assertEquals("I wish you a merry christmas!", (String)
entries.get("text"));
+ }
+}
Index: src/java/org/apache/commons/i18n/XMLMessageProvider.java
===================================================================
--- src/java/org/apache/commons/i18n/XMLMessageProvider.java
(revision 201952)
+++ src/java/org/apache/commons/i18n/XMLMessageProvider.java
(working copy)
@@ -123,9 +123,11 @@
private Message lookupMessage(String id, Locale locale) {
String key = id + '_' + locale.toString();
if (messages.containsKey(key)) return (Message)
messages.get(key);
- while (key.lastIndexOf('_') > 0) {
- key = key.substring(0, key.lastIndexOf('_'));
+ locale = I18nUtils.getParentLocale(locale);
+ while (locale != null) {
+ key = id + '_' + locale.toString();
if (messages.containsKey(key)) return (Message)
messages.get(key);
+ locale = I18nUtils.getParentLocale(locale);
}
return null;
}
Index: src/java/org/apache/commons/i18n/I18nUtils.java
===================================================================
--- src/java/org/apache/commons/i18n/I18nUtils.java (revision 0)
+++ src/java/org/apache/commons/i18n/I18nUtils.java (revision 0)
@@ -0,0 +1,40 @@
+/*
+*
+*
====================================================================
+*
+* Copyright 2004 The Apache Software Foundation
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
+* See the License for the specific language governing permissions
and
+* limitations under the License.
+*
+*/
+package org.apache.commons.i18n;
+
+import java.util.Locale;
+
+/**
+ * This class holds utility methods useful when working with i18n.
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtils {
+ private I18nUtils() {
+ }
+
+ public static Locale getParentLocale (Locale locale) {
+ if(locale.getVariant().length() != 0)
+ return new Locale(locale.getLanguage(),
locale.getCountry());
+ else if(locale.getCountry().length() != 0)
+ return new Locale(locale.getLanguage());
+ else // Locale with only language have no parent
+ return null;
+ }
+}
\ No newline at end of file
Index: src/java/org/apache/commons/i18n/JdbcMessageProvider.java
===================================================================
--- src/java/org/apache/commons/i18n/JdbcMessageProvider.java
(revision 0)
+++ src/java/org/apache/commons/i18n/JdbcMessageProvider.java
(revision 0)
@@ -0,0 +1,183 @@
+package org.apache.commons.i18n;
+
+import javax.sql.DataSource;
+import java.util.*;
+import java.sql.*;
+
+/**
+ * @author Mattias Jiderhamn
+ */
+public class JdbcMessageProvider implements MessageProvider {
+ /**
+ * This Map has locale or language as key, and a Map with the
different
+ * messages as value.
+ */
+ private final Map locales = new HashMap();
+
+ private String idColumn;
+
+ private String languageColumn;
+
+ public JdbcMessageProvider(Connection conn, String table,
String idColumn, String languageColumn)
+ throws SQLException {
+ this.idColumn = idColumn;
+ this.languageColumn = languageColumn;
+ init(conn, table);
+ }
+
+ public JdbcMessageProvider(DataSource ds, String table,
String idColumn, String languageColumn)
+ throws SQLException {
+ this.idColumn = idColumn;
+ this.languageColumn = languageColumn;
+ Connection conn = null;
+ try {
+ conn = ds.getConnection();
+ init(conn, table);
+ }
+ finally {
+ if(conn != null)
+ conn.close();
+ }
+ }
+
+ /**
+ * Create JDBC MessageProvider from properties in a Map, such
+ * as a java.util.Properties object. The following are the
properties in use, which
+ * are the same as for JDBCResources of Jakarta Commons
Resources
+ * jdbc.connect.driver = org.gjt.mm.mysql.Driver
+ * jdbc.connect.url = jdbc:mysql://localhost/
resources
+ * jdbc.connect.login = resourcesTest
+ * jdbc.connect.password = resourcesTest
+ *
+ * jdbc.sql.table = resources
+ * jdbc.sql.locale.column = locale
+ * jdbc.sql.key.column = msgKey
+ */
+ public JdbcMessageProvider(Map properties) throws
ClassNotFoundException, SQLException {
+ String driver = (String)properties.get
("jdbc.connect.driver");
+ String url = (String)properties.get("jdbc.connect.url");
+ String user = (String)properties.get("jdbc.connect.login");
+ String pass = (String)properties.get
("jdbc.connect.password");
+
+ String table = (String)properties.get("jdbc.sql.table");
+ this.idColumn = (String)properties.get
("jdbc.sql.key.column");
+ this.languageColumn = (String)properties.get
("jdbc.sql.locale.column");
+
+ Class.forName(driver);
+ Connection conn = null;
+ try {
+ conn = DriverManager.getConnection(url, user, pass);
+ init(conn, table);
+ }
+ finally {
+ if(conn != null)
+ conn.close();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////
///////
+ // Methods for initialization
+ ////////////////////////////////////////////////////////////////
///////
+
+ private void init(Connection conn, String table) throws
SQLException {
+ Statement stmt = null;
+ ResultSet rs = null;
+ try {
+ stmt = conn.createStatement();
+ rs = stmt.executeQuery("SELECT * FROM " + table);
+ String[] valueColumns = getValueColumns(rs);
+ while(rs.next()) {
+ String id = rs.getString(idColumn);
+ Locale locale = getLocale(rs);
+ Map entries = new HashMap();
+ for(int i = 0; i < valueColumns.length; i++) {
+ entries.put(valueColumns[i], rs.getString(valueColumns[i]));
+ }
+ Map localeMap = (Map)locales.get(locale);
+ if(localeMap == null) { // If first record for
this Locale
+ localeMap = new HashMap();
+ locales.put(locale, localeMap);
+ }
+ localeMap.put(id, entries);
+ }
+ }
+ finally {
+ if(stmt != null)
+ stmt.close();
+ if(rs != null)
+ rs.close();
+ }
+ }
+
+ /**
+ * Get a String of all the column names, except the ID column
and the
+ * language column.
+ * @param rs A <code>ResultSet</code> ready for reading meta
data.
+ * @return A String array with the text value column names.
+ * @throws SQLException If an SQL error occurs.
+ */
+ protected String[] getValueColumns(ResultSet rs) throws
SQLException {
+ List output = new LinkedList();
+ ResultSetMetaData metadata = rs.getMetaData();
+ int count = metadata.getColumnCount();
+ for(int i = 0; i < count; i++) {
+ String columnName = metadata.getColumnName(i+1); //
(Count from 1)
+ if(! idColumn.equals(columnName) && !
languageColumn.equals(columnName) )
+ output.add(columnName);
+ }
+ return (String[])output.toArray(new String[0]);
+ }
+
+ /**
+ * Get <code>Locale</code> for the current record in the
ResultSet. May be overridden
+ * by subclasses to allow for proprietary interpretation of
language data.
+ * The default implementation assumes the column with the
name provided as languageColumn
+ * for the constructor contains the ISO-639 code.
+ * @return The <code>Locale</code> of the current
<code>ResultSet</code> record.
+ */
+ protected Locale getLocale(ResultSet rs) throws SQLException {
+ return new Locale(rs.getString(languageColumn));
+ }
+
+ ////////////////////////////////////////////////////////////////
///////
+ // Methods to implement MessageProvider
+ ////////////////////////////////////////////////////////////////
///////
+
+ public String getText(String id, String entry, Locale locale)
throws MessageNotFoundException {
+ // TODO: Add Logging
+ Map entries = getEntries(id, locale);
+ if(entries != null) {
+ // TODO: Consider whether we need to recurse up if
entries does not contain requested entry
+ return (String)entries.get(entry);
+ }
+ else
+ return null;
+ }
+
+ public Map getEntries(String id, Locale locale) throws
MessageNotFoundException {
+ Map entries = findEntriesRecursively(id,locale);
+ if(entries == null) // If not found by using specified
locale, try to use default
+ entries = findEntriesRecursively(id,Locale.getDefault
());
+ return entries;
+ }
+
+ /**
+ * Find entries by looking at the parent locale (language,
country, variant ->
+ * language, country -> language) until entry is found. If
entry not found for topmost
+ * Locale (language only), null is returned.
+ */
+ private Map findEntriesRecursively(String id, Locale locale) {
+ Map localeIds = (Map)locales.get(locale);
+ if(localeIds != null) {
+ Map entries = (Map)localeIds.get(id);
+ if(entries != null)
+ return entries;
+ }
+ Locale parentLocale = I18nUtils.getParentLocale(locale);
+ if(parentLocale == null)
+ return null;
+ else
+ return findEntriesRecursively(id, parentLocale); //
Recursive call
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]