001 /* 002 Copyright (C) 2001-2003 Lionel Seinturier <Lionel.Seinturier@lip6.fr> 003 004 This program is free software; you can redistribute it and/or modify 005 it under the terms of the GNU Lesser General Public License as 006 published by the Free Software Foundation; either version 2 of the 007 License, or (at your option) any later version. 008 009 This program is distributed in the hope that it will be useful, 010 but WITHOUT ANY WARRANTY; without even the implied warranty of 011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 GNU Lesser General Public License for more details. 013 014 You should have received a copy of the GNU Lesser General Public License 015 along with this program; if not, write to the Free Software 016 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 017 018 package org.objectweb.jac.aspects.distrans.persistence; 019 020 import org.objectweb.jac.core.NameRepository; 021 import org.objectweb.jac.core.rtti.ClassItem; 022 import org.objectweb.jac.core.rtti.ClassRepository; 023 import org.objectweb.jac.core.rtti.FieldItem; 024 import org.objectweb.jac.util.Repository; 025 026 import java.sql.Connection; 027 import java.sql.PreparedStatement; 028 import java.sql.ResultSet; 029 import java.sql.SQLException; 030 import java.sql.Statement; 031 import java.util.Collection; 032 import java.util.HashMap; 033 import java.util.Iterator; 034 import java.util.Map; 035 036 import javax.transaction.HeuristicMixedException; 037 import javax.transaction.HeuristicRollbackException; 038 import javax.transaction.NotSupportedException; 039 import javax.transaction.RollbackException; 040 import javax.transaction.SystemException; 041 042 import org.enhydra.jdbc.standard.StandardXADataSource; 043 044 /** 045 * Basic transaction-enabled persistence storage. 046 * 047 * Data is stored in SQL tables. 048 * Each table contains object field values and owns one more attribute 049 * than its associated class contains field. The additional attribute 050 * stores the object name. 051 * 052 * @author Lionel Seinturier <Lionel.Seinturier@lip6.fr> 053 * @version 1.0 054 */ 055 public class SimpleDbPersistence implements PersistenceItf { 056 057 /** 058 * The name of the attribute storing the persistent object name 059 * in SQL tables. 060 */ 061 final static private String objectAttributeName = "_objname"; 062 063 064 /** 065 * Create a SQL table to hold persistent data. 066 * Implements PersistenceItf#createStorageIfNeeded() 067 * 068 * The table is not created if it already exists. This enables to reuse 069 * existing tables to map their content to JAC objects. However, this may 070 * cause problems if the schema is different than the expected one. In 071 * such a case, call createStorage instead. 072 * 073 * @param className the class name for which we want to create a table 074 * @param ds the data source 075 */ 076 public void initStorageIfNeeded( String className, StandardXADataSource ds ) { 077 try { 078 _initStorageIfNeeded(className,ds); 079 } catch (SQLException e) { 080 e.printStackTrace(); 081 System.exit(1); 082 } 083 } 084 085 private void _initStorageIfNeeded( String className, StandardXADataSource ds ) 086 throws SQLException { 087 088 /** 089 * Check whether a table exists for each element of classNames. 090 * The table name is the class name where dots delimiting package 091 * names are replaced by underscores. 092 */ 093 Connection connection = XAPoolCache.getConnection(ds); 094 095 String tableName = className.replace('.','_'); 096 Statement st = connection.createStatement(); 097 try { 098 st.executeQuery("SELECT * FROM "+tableName); 099 } 100 catch (SQLException sqle) { 101 /** 102 * If the table does not exist, the expected behavior is 103 * to throw a SQLException. In such a case, create the table. 104 */ 105 initStorage(className,connection); 106 } 107 } 108 109 110 /** 111 * Create a SQL table to hold persistent data. 112 * If the table exists, it is deleted first. 113 * Implements PersistenceItf#createStorage() 114 * 115 * @param className the class name for which we want to create a table 116 * @param ds the data source 117 */ 118 public void initStorage( String className, StandardXADataSource ds ) { 119 try { 120 _initStorage(className,ds); 121 } catch (SQLException e) { 122 e.printStackTrace(); 123 System.exit(1); 124 } 125 } 126 127 private void _initStorage( String className, StandardXADataSource ds ) 128 throws SQLException { 129 130 /** 131 * Check whether a table exists for each element of classNames. 132 */ 133 Connection connection = XAPoolCache.getConnection(ds); 134 135 /** 136 * The table name is the class name where dots delimiting package 137 * names are replaced by underscores. 138 */ 139 String tableName = className.replace('.','_'); 140 Statement st = connection.createStatement(); 141 try { 142 st.executeQuery("DROP TABLE "+tableName); 143 } 144 catch (SQLException sqle) { 145 /** 146 * If the table does not exist, the expected behavior is 147 * to throw a SQLException. 148 */ 149 } 150 151 initStorage(className,connection); 152 } 153 154 155 /** 156 * Create a SQL table to hold persistent data. 157 * 158 * @param className the class name 159 * @param connection the connection towards the database 160 */ 161 private void initStorage( String className, Connection connection ) 162 throws SQLException { 163 164 /** 165 * The table name is the class name where dots delimiting package names 166 * are replaced by underscores. 167 */ 168 String tableName = className.replace('.','_'); 169 170 String sql = 171 "CREATE TABLE "+tableName+" ( "+ 172 objectAttributeName+" VARCHAR PRIMARY KEY"; 173 174 ClassItem classItem = classes.getClass(className); 175 Collection fields = classItem.getAllFields(); 176 for (Iterator iter = fields.iterator(); iter.hasNext();) { 177 FieldItem field = (FieldItem) iter.next(); 178 String fieldName = field.getName(); 179 String fieldType = field.getType().getName(); 180 String sqlType = (String) SimpleDbPersistence.javaTypesToSQL.get(fieldType); 181 sql += ", "+fieldName+" "+sqlType; 182 } 183 sql += ");"; 184 185 Statement st = connection.createStatement(); 186 st.executeUpdate(sql); 187 st.close(); 188 } 189 190 /** 191 * Store mappings between Java types and SQL ones. 192 * For now on, this is only a quick-and-dirty partial mapping. 193 * To be completed. 194 */ 195 private static Map javaTypesToSQL = new HashMap(); 196 static { 197 javaTypesToSQL.put("java.lang.String","VARCHAR"); 198 javaTypesToSQL.put("int","INT"); 199 javaTypesToSQL.put("double","DOUBLE PRECISION"); 200 } 201 202 203 /** 204 * Store an object into the persistence storage. 205 * Implements PersistenceItf#load() 206 * 207 * @param wrappee the object to store 208 * @param name the identifier for the object 209 * @param ds the data source 210 */ 211 public void load( Object wrappee, String name, StandardXADataSource ds ) 212 throws SQLException, IllegalArgumentException, IllegalAccessException, NotSupportedException, SecurityException, IllegalStateException, RollbackException, HeuristicMixedException, HeuristicRollbackException, SystemException { 213 214 ClassItem classItem = classes.getClass(wrappee); 215 216 /** 217 * Get a prepared statement to fetch the data. 218 * It may be cached in the selectStatements map. 219 */ 220 Connection connection = XAPoolCache.getConnection(ds); 221 222 PreparedStatement ps = 223 createSelectStatement(connection,classItem); 224 ps.setString(1,name); 225 ResultSet rs = ps.executeQuery(); 226 227 /** 228 * The object hasn't been found in the storage. 229 * The most likely reason is that it has never been 230 * written before. In such a case, do nothing (ie don't 231 * reflect any value on the wrappee). 232 */ 233 boolean found = rs.next(); 234 if (!found) return; 235 236 /** 237 * Set fields with values retrieved from the ResultSet. 238 * Start at index 2 to skip the wrappeeName. 239 */ 240 Collection fields = classItem.getAllFields(); 241 int i=2; 242 for (Iterator iter = fields.iterator(); iter.hasNext();i++) { 243 FieldItem field = (FieldItem) iter.next(); 244 Object value = rs.getObject(i); 245 field.set(wrappee,value); 246 } 247 rs.close(); 248 } 249 250 251 /** 252 * Update an object with the values retrieved from the persistent 253 * storage. 254 * Implements PersistenceItf#store() 255 * 256 * @param wrappee the object to update 257 * @param name the identifier for the object 258 * @param ds the data source 259 */ 260 public void store( Object wrappee, String name, StandardXADataSource ds ) 261 throws Exception { 262 263 ClassItem classItem = classes.getClass(wrappee); 264 265 /** 266 * Save the wrappee field values into the database. 267 * Get a prepared statement to update the data. 268 * It may be cached in the updateStatements map. 269 */ 270 Connection connection = XAPoolCache.getConnection(ds); 271 272 PreparedStatement ps = 273 createUpdateStatement(connection,classItem); 274 275 Collection fields = classItem.getAllFields(); 276 int i=1; 277 for (Iterator iter = fields.iterator(); iter.hasNext();i++) { 278 FieldItem field = (FieldItem) iter.next(); 279 Object value = field.get(wrappee); 280 ps.setObject(i,value); 281 } 282 ps.setString(i,name); 283 284 /** 285 * Update the data in the table. 286 * If 0 row have been updates, the row didn't exist yet, create it. 287 */ 288 int rowCount = ps.executeUpdate(); 289 290 if ( rowCount == 0 ) { 291 PreparedStatement insertps = 292 createInsertStatement( 293 connection,classItem,wrappee,name 294 ); 295 insertps.executeUpdate(); 296 insertps.close(); 297 ps.executeUpdate(); 298 } 299 } 300 301 /** 302 * Create an INSERT SQL request to store the field values of an object. 303 * Each field is mapped onto a SQL attribute. 304 * The table contains one more attribute which is the name of the wrappee, 305 * and which is also the primary key of the table. 306 * 307 * @param connection the connection towards the database 308 * @param cl the class 309 * @param wrappee the persistent object (ie the wrappee) 310 * @param wrappeeName the wrappee name 311 * @return the prepared statement 312 */ 313 private static PreparedStatement createInsertStatement( 314 Connection connection, ClassItem cl, 315 Object wrappee, String wrappeeName ) throws SQLException { 316 317 /** 318 * The table name is the class name where dots delimiting package names 319 * are replaced by underscores. 320 */ 321 String tableName = cl.getName(); 322 tableName = tableName.replace('.','_'); 323 String request = "INSERT INTO "+tableName+" VALUES (?"; 324 325 Collection fields = cl.getAllFields(); 326 for (Iterator iter = fields.iterator(); iter.hasNext();) { 327 iter.next(); 328 request += ",?"; 329 } 330 331 request += ");"; 332 333 PreparedStatement ps = connection.prepareStatement(request); 334 335 ps.setString(1,wrappeeName); 336 int i=2; 337 for (Iterator iter = fields.iterator(); iter.hasNext();i++) { 338 iter.next(); 339 ps.setObject(i,null); 340 } 341 342 return ps; 343 } 344 345 /** 346 * Create a SELECT SQL request to fetch the field values of an object. 347 * Each field is mapped onto a SQL attribute. 348 * The table contains one more attribute which is the name of the wrappee, 349 * and which is also the primary key of the table. 350 * 351 * @param connection the connection towards the database 352 * @param cl the class 353 * @return the prepared statement 354 */ 355 static private PreparedStatement createSelectStatement( 356 Connection connection, ClassItem cl ) throws SQLException { 357 358 /** 359 * The table name is the class name where dots delimiting package names 360 * are replaced by underscores. 361 */ 362 String tableName = cl.getName(); 363 tableName = tableName.replace('.','_'); 364 String request = 365 "SELECT * FROM "+tableName+" WHERE "+ 366 objectAttributeName+"=?;"; 367 368 return connection.prepareStatement(request); 369 } 370 371 /** 372 * Create an UPDATE SQL request to store the field values of an object. 373 * Each field is mapped onto a SQL attribute. 374 * The table contains one more attribute which is the name of the wrappee, 375 * and which is also the primary key of the table. 376 * 377 * @param connection the connection towards the database 378 * @param cl the class 379 * @return the prepared statement 380 */ 381 static private PreparedStatement createUpdateStatement( 382 Connection connection, ClassItem cl ) throws SQLException { 383 384 /** 385 * The table name is the class name where dots delimiting package names 386 * are replaced by underscores. 387 */ 388 String tableName = cl.getName(); 389 tableName = tableName.replace('.','_'); 390 String request = "UPDATE "+tableName+" SET "; 391 392 Collection fields = cl.getAllFields(); 393 boolean first = true; 394 for (Iterator iter = fields.iterator(); iter.hasNext();) { 395 FieldItem field = (FieldItem) iter.next(); 396 String name = field.getName(); 397 398 if (first) first = false; 399 else request += ","; 400 request += name + "=?"; 401 } 402 403 request += " WHERE "+objectAttributeName+"=?;"; 404 405 return connection.prepareStatement(request); 406 } 407 408 /** 409 * The reference towards the RTTI class repository. 410 */ 411 private ClassRepository classes = ClassRepository.get(); 412 413 /** 414 * The reference towards the JAC name repository. 415 * Needed by applyPersistence(). 416 */ 417 private Repository names = NameRepository.get(); 418 }