Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 792   Methods: 39
NCLOC: 582   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
AdjListJDBCCacheLoader.java 66.7% 77.1% 64.1% 73.3%
coverage coverage
 1    package org.jboss.cache.loader;
 2   
 3    import net.jcip.annotations.ThreadSafe;
 4    import org.apache.commons.logging.Log;
 5    import org.jboss.cache.Fqn;
 6    import org.jboss.cache.Modification;
 7    import org.jboss.cache.config.CacheLoaderConfig;
 8    import org.jboss.cache.lock.StripedLock;
 9    import org.jboss.cache.util.Util;
 10   
 11    import java.io.ByteArrayInputStream;
 12    import java.io.IOException;
 13    import java.io.InputStream;
 14    import java.sql.Connection;
 15    import java.sql.DatabaseMetaData;
 16    import java.sql.PreparedStatement;
 17    import java.sql.ResultSet;
 18    import java.sql.SQLException;
 19    import java.sql.Statement;
 20    import java.sql.Types;
 21    import java.util.Collection;
 22    import java.util.Collections;
 23    import java.util.HashMap;
 24    import java.util.HashSet;
 25    import java.util.List;
 26    import java.util.Locale;
 27    import java.util.Map;
 28    import java.util.Properties;
 29    import java.util.Set;
 30   
 31    /**
 32    * Adjacency List Model is the model of persisting trees in which each children holds a reference to its parent.
 33    * An alternative model is the Nested Set Model (a.k.a. Modified Preorder Model) - this approach adds some additional
 34    * indexing information to each persisted node. This indexing info is further used for optimizing operations like
 35    * subtree loading, deleting etc. The indexes are update for each insertion.
 36    * <p/>
 37    * Adjacency List Model proved more performance-effective for the following reason: the entire path is persisted rather
 38    * than only a reference to parent. Looking up nodes heavily relies on that, and the performance is similar as in the
 39    * case of Modified Preorder Model. Even more there is no costly update indexes operation.
 40    *
 41    * @author Mircea.Markus@iquestint.com
 42    * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a>
 43    * @version 1.0
 44    */
 45    @ThreadSafe
 46    public abstract class AdjListJDBCCacheLoader extends AbstractCacheLoader
 47    {
 48    protected ConnectionFactory cf;
 49    protected String driverName;
 50    private AdjListJDBCCacheLoaderConfig config;
 51    protected StripedLock lock = new StripedLock();
 52   
 53  259 public void setConfig(CacheLoaderConfig.IndividualCacheLoaderConfig base)
 54    {
 55  259 config = processConfig(base);
 56   
 57  259 if (config.getDatasourceName() == null)
 58    {
 59  196 try
 60    {
 61    /* Instantiate an standalone connection factory as per configuration, either explicitly
 62    defined or the default one */
 63  196 getLogger().debug("Initialising with a connection factory since data source is not provided.");
 64  196 if (getLogger().isDebugEnabled())
 65  0 getLogger().debug("Using connection factory " + config.getConnectionFactoryClass());
 66  196 cf = (ConnectionFactory) Util.loadClass(config.getConnectionFactoryClass()).newInstance();
 67    }
 68    catch (Exception e)
 69    {
 70  0 getLogger().error("Connection factory class could not be loaded", e);
 71  0 throw new IllegalStateException("Connection factory class could not be loaded", e);
 72    }
 73    }
 74    else
 75    {
 76    /* We create the ManagedConnectionFactory instance but the JNDI lookup is no done until
 77    the start method is called, since that's when its registered in its lifecycle */
 78  63 cf = new ManagedConnectionFactory();
 79    }
 80    /* Regardless of the type of connection factory, we set the configuration */
 81  259 cf.setConfig(config);
 82    }
 83   
 84   
 85    /**
 86    * Returns a map representing a node.
 87    *
 88    * @param name node's fqn
 89    * @return node
 90    * @throws Exception
 91    */
 92  7264 public Map get(Fqn name) throws Exception
 93    {
 94  7264 lock.acquireLock(name, false);
 95  7264 try
 96    {
 97  7264 final Map node = loadNode(name);
 98  7264 return node == NULL_NODE_IN_ROW ? new HashMap(0) : node;
 99    }
 100    finally
 101    {
 102  7264 lock.releaseLock(name);
 103    }
 104    }
 105   
 106    /**
 107    * Fetches child node names (not pathes).
 108    *
 109    * @param fqn parent fqn
 110    * @return a set of child node names or null if there are not children found for the fqn
 111    * @throws Exception
 112    */
 113  6203 public Set<String> getChildrenNames(Fqn fqn) throws Exception
 114    {
 115  6203 Set children = null;
 116  6203 Connection con = null;
 117  6203 PreparedStatement ps = null;
 118  6203 ResultSet rs = null;
 119  6203 try
 120    {
 121  6203 if (getLogger().isDebugEnabled())
 122    {
 123  0 getLogger().debug("executing sql: " + config.getSelectChildNamesSql() + " (" + fqn + ")");
 124    }
 125   
 126  6203 con = cf.getConnection();
 127  6203 ps = con.prepareStatement(config.getSelectChildNamesSql());
 128  6203 ps.setString(1, fqn.toString());
 129  6203 lock.acquireLock(fqn, false);
 130  6203 rs = ps.executeQuery();
 131  6203 if (rs.next())
 132    {
 133  120 children = new HashSet();
 134  120 do
 135    {
 136  302 String child = rs.getString(1);
 137  302 int slashInd = child.lastIndexOf('/');
 138  302 String name = child.substring(slashInd + 1);
 139    //Fqn childFqn = Fqn.fromString(child);
 140    //String name = (String) childFqn.get(childFqn.size() - 1);
 141  302 children.add(name);
 142    }
 143  302 while (rs.next());
 144    }
 145    }
 146    catch (SQLException e)
 147    {
 148  0 reportAndRethrowError("Failed to get children names for fqn " + fqn, e);
 149    }
 150    finally
 151    {
 152  6203 safeClose(rs);
 153  6203 safeClose(ps);
 154  6203 cf.close(con);
 155  6203 lock.releaseLock(fqn);
 156    }
 157   
 158  6203 return children == null ? null : Collections.unmodifiableSet(children);
 159    }
 160   
 161   
 162    /**
 163    * Nullifies the node.
 164    *
 165    * @param name node's fqn
 166    * @throws Exception
 167    */
 168  47 public void removeData(Fqn name) throws Exception
 169    {
 170  47 updateNode(name, null);
 171    }
 172   
 173    /**
 174    * First phase in transaction commit process. The changes are committed if only one phase if requested.
 175    * All the modifications are committed using the same connection.
 176    *
 177    * @param tx something representing transaction
 178    * @param modifications a list of modifications
 179    * @param one_phase indicates whether it's one or two phase commit transaction
 180    * @throws Exception
 181    */
 182  19 public void prepare(Object tx, List<Modification> modifications, boolean one_phase) throws Exception
 183    {
 184    // start a tx
 185  19 cf.prepare(tx);
 186   
 187  19 try
 188    {
 189  19 put(modifications);
 190   
 191    // commit if it's one phase only
 192  19 if (one_phase)
 193    {
 194  4 commit(tx);
 195    }
 196    }
 197    catch (Exception e)
 198    {
 199    // todo should I rollback it here or rollback is supposed to be invoke by someone from outside?
 200  0 rollback(tx);
 201    // is this ok?
 202  0 throw e;
 203    }
 204    }
 205   
 206   
 207    /**
 208    * Commits a transaction.
 209    *
 210    * @param tx the tx to commit
 211    * @throws Exception
 212    */
 213  14 public void commit(Object tx) throws Exception
 214    {
 215  14 cf.commit(tx);
 216    }
 217   
 218    /**
 219    * Rolls back a transaction.
 220    *
 221    * @param tx the tx to rollback
 222    */
 223  5 public void rollback(Object tx)
 224    {
 225  5 cf.rollback(tx);
 226    }
 227   
 228    // Service implementation
 229   
 230  241 public void create() throws Exception
 231    {
 232    }
 233   
 234  253 public void start() throws Exception
 235    {
 236  253 cf.start();
 237   
 238  253 Connection con = null;
 239  253 Statement st = null;
 240   
 241  253 try
 242    {
 243  253 con = cf.getConnection();
 244  253 driverName = getDriverName(con);
 245  253 if (config.getCreateTable())
 246    {
 247  253 if (!tableExists(config.getTable(), con))
 248    {
 249  247 if (getLogger().isDebugEnabled())
 250    {
 251  0 getLogger().debug("executing ddl: " + config.getCreateTableDDL());
 252    }
 253  247 st = con.createStatement();
 254  247 st.executeUpdate(config.getCreateTableDDL());
 255    }
 256    }
 257    }
 258    finally
 259    {
 260  253 safeClose(st);
 261  253 cf.close(con);
 262    }
 263    }
 264   
 265  253 public void stop()
 266    {
 267  253 if (config.getDropTable())
 268    {
 269  253 Connection con = null;
 270  253 Statement st = null;
 271  253 try
 272    {
 273  253 if (getLogger().isDebugEnabled())
 274    {
 275  0 getLogger().debug("executing ddl: " + config.getDropTableDDL());
 276    }
 277   
 278  253 con = cf.getConnection();
 279  253 st = con.createStatement();
 280  253 st.executeUpdate(config.getDropTableDDL());
 281  247 safeClose(st);
 282    }
 283    catch (SQLException e)
 284    {
 285  6 getLogger().error("Failed to drop table: " + e.getMessage(), e);
 286    }
 287    finally
 288    {
 289  253 safeClose(st);
 290  253 cf.close(con);
 291  253 cf.stop();
 292    }
 293    }
 294    }
 295   
 296  241 public void destroy()
 297    {
 298    }
 299   
 300   
 301    /**
 302    * Checks that there is a row for the fqn in the database.
 303    *
 304    * @param name node's fqn
 305    * @return true if there is a row in the database for the given fqn even if the node column is null.
 306    * @throws Exception
 307    */
 308  5825 public boolean exists(Fqn name) throws Exception
 309    {
 310  5825 lock.acquireLock(name, false);
 311  5825 try
 312    {
 313  5825 final Map node = loadNode(name);
 314  5825 return node != null;// && node != NULL_NODE_IN_ROW;
 315    }
 316    finally
 317    {
 318  5825 lock.releaseLock(name);
 319    }
 320    }
 321   
 322    /**
 323    * Removes attribute's value for a key. If after removal the node contains no attributes, the node is nullified.
 324    *
 325    * @param name node's name
 326    * @param key attribute's key
 327    * @return removed value or null if there was no value for the passed in key
 328    * @throws Exception
 329    */
 330  6098 public Object remove(Fqn name, Object key) throws Exception
 331    {
 332  6098 lock.acquireLock(name, true);
 333  6098 try
 334    {
 335  6098 Object removedValue = null;
 336  6098 Map node = loadNode(name);
 337  6098 if (node != null && node != NULL_NODE_IN_ROW)
 338    {
 339  2899 removedValue = node.remove(key);
 340  2899 if (node.isEmpty())
 341    {
 342  2817 updateNode(name, null);
 343    }
 344    else
 345    {
 346  82 updateNode(name, node);
 347    }
 348    }
 349  6098 return removedValue;
 350    }
 351    finally
 352    {
 353  6098 lock.releaseLock(name);
 354    }
 355    }
 356   
 357   
 358    /**
 359    * Loads a node from the database.
 360    *
 361    * @param name the fqn
 362    * @return non-null Map representing the node,
 363    * null if there is no row with the fqn in the table,
 364    * NULL_NODE_IN_ROW if there is a row in the table with the fqn but the node column contains null.
 365    */
 366  32159 protected Map loadNode(Fqn name)
 367    {
 368  32159 boolean rowExists = false;
 369  32159 Map oldNode = null;
 370  32159 Connection con = null;
 371  32159 PreparedStatement ps = null;
 372  32159 ResultSet rs = null;
 373  32159 try
 374    {
 375  32159 if (getLogger().isDebugEnabled())
 376    {
 377  0 getLogger().debug("executing sql: " + config.getSelectNodeSql() + " (" + name + ")");
 378    }
 379   
 380  32159 con = cf.getConnection();
 381  32159 ps = con.prepareStatement(config.getSelectNodeSql());
 382  32159 ps.setString(1, name.toString());
 383   
 384  32159 rs = ps.executeQuery();
 385   
 386  32159 if (rs.next())
 387    {
 388  20330 rowExists = true;
 389  20330 InputStream is = rs.getBinaryStream(1);
 390  20330 if (is != null && !rs.wasNull())
 391    {
 392  15665 try
 393    {
 394    // ObjectInputStream ois = null;
 395    // ois = new ObjectInputStream(is);
 396    // Object marshalledNode = ois.readObject();
 397   
 398    // deserialize result
 399  15665 Object marshalledNode = unmarshall(is);
 400  15665 oldNode = (Map) marshalledNode;
 401    }
 402    catch (Exception e)
 403    {
 404  0 throw new Exception("Unable to load to deserialize result: ", e);
 405    }
 406    finally
 407    {
 408  15665 safeClose(is);
 409    }
 410    }
 411    }
 412    }
 413    catch (Exception e)
 414    {
 415  0 reportAndRethrowError("Failed to load node for fqn " + name, e);
 416    }
 417    finally
 418    {
 419  32159 safeClose(rs);
 420  32159 safeClose(ps);
 421  32159 cf.close(con);
 422    }
 423   
 424  32159 return oldNode == null ? (rowExists ? NULL_NODE_IN_ROW : null) : oldNode;
 425    }
 426   
 427   
 428    /**
 429    * Inserts a node into the database
 430    *
 431    * @param name the fqn
 432    * @param node the node
 433    */
 434  5300 protected void insertNode(Fqn name, Map node)
 435    {
 436  5300 Connection con = null;
 437  5300 PreparedStatement ps = null;
 438  5300 try
 439    {
 440  5300 if (getLogger().isDebugEnabled())
 441    {
 442  0 getLogger().debug("executing sql: " + config.getInsertNodeSql() + " (" + name + ")");
 443    }
 444   
 445  5300 con = cf.getConnection();
 446  5300 ps = con.prepareStatement(config.getInsertNodeSql());
 447   
 448  5300 ps.setString(1, name.toString());
 449   
 450  5300 if (node != null)
 451    {
 452    // ByteArrayOutputStream baos = new ByteArrayOutputStream();
 453    // ObjectOutputStream oos = new ObjectOutputStream(baos);
 454    // oos.writeObject(node);
 455   
 456  4537 byte[] byteStream = marshall(node);
 457  4537 ByteArrayInputStream bais = new ByteArrayInputStream(byteStream);
 458  4537 ps.setBinaryStream(2, bais, byteStream.length);
 459    }
 460    else
 461    {
 462    // a hack to handles the incomp. of SQL server jdbc driver prior to SQL SERVER 2005
 463  763 if (driverName != null && (driverName.contains("SQLSERVER")
 464    || driverName.contains("POSTGRESQL")))
 465    {
 466  0 ps.setNull(2, Types.LONGVARBINARY);
 467    }
 468    else
 469    {
 470  763 ps.setNull(2, Types.BLOB);
 471    }
 472    //ps.setNull(2, Types.LONGVARBINARY);
 473    }
 474   
 475  5300 if (name.size() == 0)
 476    {
 477  231 ps.setNull(3, Types.VARCHAR);
 478    }
 479    else
 480    {
 481  5069 ps.setString(3, name.getAncestor(name.size() - 1).toString());
 482    }
 483   
 484  5300 int rows = ps.executeUpdate();
 485  5300 if (rows != 1)
 486    {
 487  0 throw new IllegalStateException("Expected one insert row but got " + rows);
 488    }
 489    }
 490    catch (RuntimeException e)
 491    {
 492  0 throw e;
 493    }
 494    catch (Exception e)
 495    {
 496  0 getLogger().error("Failed to insert node :" + e.getMessage());
 497  0 throw new IllegalStateException("Failed to insert node: " + e.getMessage(), e);
 498    }
 499    finally
 500    {
 501  5300 safeClose(ps);
 502  5300 cf.close(con);
 503    }
 504    }
 505   
 506   
 507    /**
 508    * Updates a node in the database.
 509    *
 510    * @param name the fqn
 511    * @param node new node value
 512    */
 513  11137 protected void updateNode(Fqn name, Map node)
 514    {
 515  11137 Connection con = null;
 516  11137 PreparedStatement ps = null;
 517  11137 try
 518    {
 519  11137 if (getLogger().isDebugEnabled())
 520    {
 521  0 getLogger().debug("executing sql: " + config.getUpdateNodeSql());
 522    }
 523   
 524  11137 con = cf.getConnection();
 525  11137 ps = con.prepareStatement(config.getUpdateNodeSql());
 526   
 527  11137 if (node == null)
 528    {
 529    //ps.setNull(1, Types.BLOB);
 530    // ps.setNull(1, Types.LONGVARBINARY);
 531    // don't set it to null - simply use an empty hash map.
 532  2868 node = new HashMap(0);
 533    }
 534   
 535    // ByteArrayOutputStream baos = new ByteArrayOutputStream();
 536    // ObjectOutputStream oos = new ObjectOutputStream(baos);
 537    // oos.writeObject(node);
 538   
 539  11137 byte[] byteStream = marshall(node);
 540  11137 ByteArrayInputStream bais = new ByteArrayInputStream(byteStream);
 541  11137 ps.setBinaryStream(1, bais, byteStream.length);
 542   
 543  11137 ps.setString(2, name.toString());
 544   
 545  11137 int rows = ps.executeUpdate();
 546    // if (rows != 1)
 547    // {
 548    // throw new IllegalStateException("Expected one updated row but got " + rows);
 549    // }
 550    }
 551    catch (Exception e)
 552    {
 553  0 reportAndRethrowError("Failed to update node for fqn " + name, e);
 554    }
 555    finally
 556    {
 557  11137 safeClose(ps);
 558  11137 cf.close(con);
 559    }
 560    }
 561   
 562  253 protected String getDriverName(Connection con)
 563    {
 564  0 if (con == null) return null;
 565  253 try
 566    {
 567  253 DatabaseMetaData dmd = con.getMetaData();
 568  253 return toUpperCase(dmd.getDriverName());
 569    }
 570    catch (SQLException e)
 571    {
 572    // This should not happen. A J2EE compatiable JDBC driver is
 573    // required to fully support metadata.
 574  0 throw new IllegalStateException("Error while getting the driver name", e);
 575    }
 576    }
 577   
 578  792 static String getRequiredProperty(Properties props, String name)
 579    {
 580  792 String value = props.getProperty(name);
 581  792 if (value == null)
 582    {
 583  0 throw new IllegalStateException("Missing required property: " + name);
 584    }
 585  792 return value;
 586    }
 587   
 588  253 protected boolean tableExists(String tableName, Connection con)
 589    {
 590  253 ResultSet rs = null;
 591  253 try
 592    {
 593    // (a j2ee spec compatible jdbc driver has to fully
 594    // implement the DatabaseMetaData)
 595  253 DatabaseMetaData dmd = con.getMetaData();
 596  253 String catalog = con.getCatalog();
 597  253 String schema = null;
 598  253 String quote = dmd.getIdentifierQuoteString();
 599  253 if (tableName.startsWith(quote))
 600    {
 601  0 if (!tableName.endsWith(quote))
 602    {
 603  0 throw new IllegalStateException("Mismatched quote in table name: " + tableName);
 604    }
 605  0 int quoteLength = quote.length();
 606  0 tableName = tableName.substring(quoteLength, tableName.length() - quoteLength);
 607  0 if (dmd.storesLowerCaseQuotedIdentifiers())
 608    {
 609  0 tableName = toLowerCase(tableName);
 610    }
 611  0 else if (dmd.storesUpperCaseQuotedIdentifiers())
 612    {
 613  0 tableName = toUpperCase(tableName);
 614    }
 615    }
 616    else
 617    {
 618  253 if (dmd.storesLowerCaseIdentifiers())
 619    {
 620  0 tableName = toLowerCase(tableName);
 621    }
 622  253 else if (dmd.storesUpperCaseIdentifiers())
 623    {
 624  253 tableName = toUpperCase(tableName);
 625    }
 626    }
 627   
 628  253 int dotIndex;
 629  ? if ((dotIndex = tableName.indexOf('.')) != -1)
 630    {
 631    // Yank out schema name ...
 632  0 schema = tableName.substring(0, dotIndex);
 633  0 tableName = tableName.substring(dotIndex + 1);
 634    }
 635   
 636  253 rs = dmd.getTables(catalog, schema, tableName, null);
 637  253 return rs.next();
 638    }
 639    catch (SQLException e)
 640    {
 641    // This should not happen. A J2EE compatiable JDBC driver is
 642    // required fully support metadata.
 643  0 throw new IllegalStateException("Error while checking if table aleady exists " + tableName, e);
 644    }
 645    finally
 646    {
 647  253 safeClose(rs);
 648    }
 649    }
 650   
 651   
 652    protected abstract Log getLogger();
 653   
 654    protected abstract AdjListJDBCCacheLoaderConfig processConfig(CacheLoaderConfig.IndividualCacheLoaderConfig base);
 655   
 656  0 protected void reportAndRethrowError(String message, Exception cause) throws IllegalStateException
 657    {
 658  0 getLogger().error(message, cause);
 659  0 throw new IllegalStateException(message, cause);
 660    }
 661   
 662  15665 protected void safeClose(InputStream is)
 663    {
 664  15665 if (is != null)
 665    {
 666  15665 try
 667    {
 668  15665 is.close();
 669    }
 670    catch (IOException e)
 671    {
 672  0 getLogger().warn("Failed to close input stream: " + e.getMessage());
 673    }
 674    }
 675    }
 676   
 677  62462 protected void safeClose(Statement st)
 678    {
 679  62462 if (st != null)
 680    {
 681  62456 try
 682    {
 683  62456 st.close();
 684    }
 685    catch (SQLException e)
 686    {
 687  0 getLogger().warn("Failed to close statement: " + e.getMessage());
 688    }
 689    }
 690    }
 691   
 692  38877 protected void safeClose(ResultSet rs)
 693    {
 694  38877 if (rs != null)
 695    {
 696  38877 try
 697    {
 698  38877 rs.close();
 699    }
 700    catch (SQLException e)
 701    {
 702  0 getLogger().warn("Failed to close result set: " + e.getMessage());
 703    }
 704    }
 705    }
 706   
 707  15683 protected Object unmarshall(InputStream from) throws Exception
 708    {
 709  15683 return getMarshaller().objectFromStream(from);
 710    }
 711   
 712  15663 protected byte[] marshall(Object obj) throws Exception
 713    {
 714  15663 return getMarshaller().objectToByteBuffer(obj);
 715    }
 716   
 717  506 private static String toUpperCase(String s)
 718    {
 719  506 return s.toUpperCase(Locale.ENGLISH);
 720    }
 721   
 722  0 private static String toLowerCase(String s)
 723    {
 724  0 return s.toLowerCase((Locale.ENGLISH));
 725    }
 726   
 727    // Inner
 728   
 729    protected static final Map NULL_NODE_IN_ROW = new Map()
 730    {
 731  0 public int size()
 732    {
 733  0 throw new UnsupportedOperationException();
 734    }
 735   
 736  0 public void clear()
 737    {
 738  0 throw new UnsupportedOperationException();
 739    }
 740   
 741  0 public boolean isEmpty()
 742    {
 743  0 throw new UnsupportedOperationException();
 744    }
 745   
 746  0 public boolean containsKey(Object key)
 747    {
 748  0 throw new UnsupportedOperationException();
 749    }
 750   
 751  0 public boolean containsValue(Object value)
 752    {
 753  0 throw new UnsupportedOperationException();
 754    }
 755   
 756  0 public Collection values()
 757    {
 758  0 throw new UnsupportedOperationException();
 759    }
 760   
 761  0 public void putAll(Map t)
 762    {
 763  0 throw new UnsupportedOperationException();
 764    }
 765   
 766  0 public Set entrySet()
 767    {
 768  0 throw new UnsupportedOperationException();
 769    }
 770   
 771  0 public Set keySet()
 772    {
 773  0 throw new UnsupportedOperationException();
 774    }
 775   
 776  0 public Object get(Object key)
 777    {
 778  0 throw new UnsupportedOperationException();
 779    }
 780   
 781  0 public Object remove(Object key)
 782    {
 783  0 throw new UnsupportedOperationException();
 784    }
 785   
 786  0 public Object put(Object key, Object value)
 787    {
 788  0 throw new UnsupportedOperationException();
 789    }
 790    };
 791   
 792    }