View Javadoc

1   /*
2    * $Id: JDBCResources.java 354539 2005-12-06 20:40:16Z niallp $
3    * $Revision: 354539 $
4    * $Date: 2005-12-06 20:40:16 +0000 (Tue, 06 Dec 2005) $
5    *
6    * ====================================================================
7    *
8    *  Copyright 2003-2005 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   *
22   */
23  
24  package org.apache.commons.resources.impl;
25  
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.net.URL;
30  import java.sql.Connection;
31  import java.sql.DriverManager;
32  import java.sql.PreparedStatement;
33  import java.sql.ResultSet;
34  import java.sql.SQLException;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.Properties;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  
42  /**
43   * <p>Concrete implementation of 
44   * {@link org.apache.commons.resources.Resources} that retrieves message
45   * key value pairs using JDBC. For this specific implementation, database
46   * connection properties and <code>Locale</code> for which the document's
47   * messages apply are specified via property files. Resources are looked up in
48   * a hierarchy of database values in a manner identical to that 
49   * performed by <code>java.util.ResourceBundle.getBundle().</code>.
50   * 
51   * </p>
52   *
53   * <p>The base URL passed to our constructor must contain the base name
54   * of a properties file that holds the JDBC datasource configuration.</p>
55   * 
56   * <p>
57   * The expected format of the required properties file might look like this:<br>
58   * <code><pre>
59   * jdbc.connect.driver               = org.gjt.mm.mysql.Driver
60   * jdbc.connect.url                  = jdbc:mysql://localhost/resources
61   * jdbc.connect.login                = resourcesTest
62   * jdbc.connect.password             = resourcesTest
63   * 
64   * jdbc.sql.db                       = resources
65   * jdbc.sql.table                    = resources
66   * jdbc.sql.locale.column            = locale
67   * jdbc.sql.key.column               = msgKey
68   * jdbc.sql.val.column               = val
69   * 
70   * org.apache.commons.resource.CACHE = true
71   * 
72   * </pre></code>
73   * 
74   * </p>
75   *
76   * @author James Mitchell
77   * @version $Revision: 354539 $ $Date: 2005-12-06 20:40:16 +0000 (Tue, 06 Dec 2005) $
78   */
79  public class JDBCResources extends CollectionResourcesBase {
80  
81      /**
82       * <p>The <code>Log</code> instance for this class.</p>
83       */
84      private transient Log log = LogFactory.getLog(JDBCResources.class);
85  
86      // ----------------------------------------------------------- Constructors
87  
88      /**
89       * <p>Create a new 
90       * {@link org.apache.commons.resources.Resources} instance with the specified
91       * logical name and base resource URL.</p>
92       *
93       * @param name Logical name of the new instance
94       * @param base Base URL of the JDBC configuration properties.
95       */
96      public JDBCResources(String name, String base) {
97          super(name, base);
98      }
99  
100 
101     // ------------------------------------------------------ Protected Methods
102 
103 
104     /**
105      * <p>Return a <code>Map</code> containing the name-value mappings for
106      * the specified base URL and requested <code>Locale</code>, if there
107      * are any.  If there are no defined mappings for the specified
108      * <code>Locale</code>, return an empty <code>Map</code> instead.</p>
109      *
110      * <p>Concrete subclasses must override this method to perform the
111      * appropriate lookup.  A typical implementation will construct an
112      * absolute URL based on the specified base URL and <code>Locale</code>,
113      * retrieve the specified resource file (if any), and parse it into
114      * a <code>Map</code> structure.</p>
115      *
116      * <p>Caching of previously retrieved <code>Map</code>s (if any) should
117      * be performed by callers of this method.  Therefore, this method should
118      * always attempt to retrieve the specified resource and load it
119      * appropriately.</p>
120      *
121      * @param baseUrl Base URL of the resource files for this 
122      * {@link org.apache.commons.resources.Resources} instance
123      * @param locale <code>Locale</code> for which name-value mappings
124      *  are requested
125      * @return A name-value Map for the specified URL and locale.
126      */
127     protected Map getLocaleMap(String baseUrl, Locale locale) {
128 
129         if (getLog().isDebugEnabled()) {
130             getLog().debug("Loading database configuration'" + locale + "' resources from base '" +
131                     baseUrl + "'");
132         }
133 
134         Properties props = new Properties();
135         //getLocaleSuffix(locale) + 
136         String name = baseUrl + ".properties";
137         InputStream stream = null;
138 
139         try {
140 
141             // Open an input stream to the URL for this locale (if any)
142             if (getLog().isTraceEnabled()) {
143                 getLog().trace("Absolute URL is '" + name + "'");
144             }
145             URL url = new URL(name);
146             stream = url.openStream();
147 
148             // Parse the input stream and populate the name-value mappings map
149             if (getLog().isTraceEnabled()) {
150                 getLog().trace("Parsing input resource");
151             }
152             props.load(stream);
153 
154         } catch (FileNotFoundException e) {
155 
156             // Log and swallow this exception
157             if (getLog().isDebugEnabled()) {
158                 getLog().debug("No resources for locale '" + locale +
159                           "' from base '" + baseUrl + "'");
160             }
161             props.clear();
162 
163         } catch (IOException e) {
164 
165             getLog().warn("IOException loading locale '" + locale +
166                      "' from base '" + baseUrl + "'", e);
167             props.clear();
168 
169         } finally {
170 
171             // Close the input stream that was opened earlier
172             if (stream != null) {
173                 try {
174                     stream.close();
175                 } catch (IOException e) {
176                     getLog().error("Error closing stream.", e);
177                 }
178                 stream = null;
179             }
180 
181         }
182 
183         // Return the populated (or empty) properties
184         Properties properties = new Properties();
185         try {
186             properties = loadData(locale, props);
187 
188         } catch (InstantiationException e) {
189             getLog().warn("InstantiationException: locale= '" + locale +
190                      "' base= '" + baseUrl + "'", e);
191         } catch (IllegalAccessException e) {
192             getLog().warn("IllegalAccessException: locale= '" + locale +
193                     "' base= '" + baseUrl + "'", e);
194         } catch (ClassNotFoundException e) {
195             getLog().warn("Specified Driver not found, make sure it is on " +
196                     "the classpath: locale= '" + locale +
197                     "' base= '" + baseUrl + "'", e);
198         } catch (SQLException e) {
199             getLog().warn("SQLException: locale= '" + locale +
200                     "' base= '" + baseUrl + "'", e);
201         }
202         return properties;
203 
204         
205 
206     }
207 
208     /**
209      * @param locale <code>Locale</code> for which name-value mappings
210      *  are requested
211      * @param connectionProps The connection properties used to instantiate
212      * a JDBC connection.
213      */
214     private Properties loadData(Locale locale, Properties connectionProps) 
215         throws InstantiationException, IllegalAccessException, 
216         ClassNotFoundException, SQLException {
217         
218         String driver = connectionProps.getProperty("jdbc.connect.driver");
219         String url    = connectionProps.getProperty("jdbc.connect.url");
220         String user = connectionProps.getProperty("jdbc.connect.login");
221         String pass = connectionProps.getProperty("jdbc.connect.password");
222         
223         String table = connectionProps.getProperty("jdbc.sql.table");
224         String localeColumn = connectionProps.getProperty("jdbc.sql.locale.column");
225         String keyColumn    = connectionProps.getProperty("jdbc.sql.key.column");
226         String valColumn    = connectionProps.getProperty("jdbc.sql.val.column");
227         Properties pairs = new Properties();
228         
229         Connection con = null;
230         PreparedStatement stmt = null;
231         ResultSet rs = null;
232         try {
233             Class.forName(driver).newInstance();
234             con = DriverManager.getConnection(url, user, pass);
235 
236             String query = "SELECT " + keyColumn + ", " + valColumn + 
237                     " FROM " + table + " where " + localeColumn + "= '" + locale + "'";
238             stmt = con.prepareStatement(query);
239             rs = stmt.executeQuery();
240 
241             while (rs.next()) {
242                 pairs.put(rs.getString(keyColumn), rs.getString(valColumn));
243             }
244         } finally {
245             try {
246                 if (rs != null) {
247                     rs.close();
248                 }
249             } catch(Exception e) {
250                 getLog().warn("Error closing ResultSet: " + e);
251             }
252             try {
253                 if (stmt != null) {
254                     stmt.close();
255                 }
256             } catch(Exception e) {
257                 getLog().warn("Error closing Statement: " + e);
258             }
259             try {
260                 if (con != null) {
261                     con.close();
262                 }
263             } catch(Exception e) {
264                 getLog().warn("Error closing Connection: " + e);
265             }
266         }
267 
268         return pairs;
269     }
270 
271     /**
272      * Accessor method for Log instance.
273      *
274      * The Log instance variable is transient and
275      * accessing it through this method ensures it
276      * is re-initialized when this instance is
277      * de-serialized.
278      *
279      * @return The Log instance.
280      */
281     private Log getLog() {
282         if (log == null) {
283             log =  LogFactory.getLog(JDBCResources.class);
284         }
285         return log;
286     }
287 }