001 /* 002 * $Id: JDBCResources.java 354539 2005-12-06 20:40:16Z niallp $ 003 * $Revision: 354539 $ 004 * $Date: 2005-12-06 20:40:16 +0000 (Tue, 06 Dec 2005) $ 005 * 006 * ==================================================================== 007 * 008 * Copyright 2003-2005 The Apache Software Foundation 009 * 010 * Licensed under the Apache License, Version 2.0 (the "License"); 011 * you may not use this file except in compliance with the License. 012 * You may obtain a copy of the License at 013 * 014 * http://www.apache.org/licenses/LICENSE-2.0 015 * 016 * Unless required by applicable law or agreed to in writing, software 017 * distributed under the License is distributed on an "AS IS" BASIS, 018 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 019 * See the License for the specific language governing permissions and 020 * limitations under the License. 021 * 022 */ 023 024 package org.apache.commons.resources.impl; 025 026 import java.io.FileNotFoundException; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.net.URL; 030 import java.sql.Connection; 031 import java.sql.DriverManager; 032 import java.sql.PreparedStatement; 033 import java.sql.ResultSet; 034 import java.sql.SQLException; 035 import java.util.Locale; 036 import java.util.Map; 037 import java.util.Properties; 038 039 import org.apache.commons.logging.Log; 040 import org.apache.commons.logging.LogFactory; 041 042 /** 043 * <p>Concrete implementation of 044 * {@link org.apache.commons.resources.Resources} that retrieves message 045 * key value pairs using JDBC. For this specific implementation, database 046 * connection properties and <code>Locale</code> for which the document's 047 * messages apply are specified via property files. Resources are looked up in 048 * a hierarchy of database values in a manner identical to that 049 * performed by <code>java.util.ResourceBundle.getBundle().</code>. 050 * 051 * </p> 052 * 053 * <p>The base URL passed to our constructor must contain the base name 054 * of a properties file that holds the JDBC datasource configuration.</p> 055 * 056 * <p> 057 * The expected format of the required properties file might look like this:<br> 058 * <code><pre> 059 * jdbc.connect.driver = org.gjt.mm.mysql.Driver 060 * jdbc.connect.url = jdbc:mysql://localhost/resources 061 * jdbc.connect.login = resourcesTest 062 * jdbc.connect.password = resourcesTest 063 * 064 * jdbc.sql.db = resources 065 * jdbc.sql.table = resources 066 * jdbc.sql.locale.column = locale 067 * jdbc.sql.key.column = msgKey 068 * jdbc.sql.val.column = val 069 * 070 * org.apache.commons.resource.CACHE = true 071 * 072 * </pre></code> 073 * 074 * </p> 075 * 076 * @author James Mitchell 077 * @version $Revision: 354539 $ $Date: 2005-12-06 20:40:16 +0000 (Tue, 06 Dec 2005) $ 078 */ 079 public class JDBCResources extends CollectionResourcesBase { 080 081 /** 082 * <p>The <code>Log</code> instance for this class.</p> 083 */ 084 private transient Log log = LogFactory.getLog(JDBCResources.class); 085 086 // ----------------------------------------------------------- Constructors 087 088 /** 089 * <p>Create a new 090 * {@link org.apache.commons.resources.Resources} instance with the specified 091 * logical name and base resource URL.</p> 092 * 093 * @param name Logical name of the new instance 094 * @param base Base URL of the JDBC configuration properties. 095 */ 096 public JDBCResources(String name, String base) { 097 super(name, base); 098 } 099 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 }