Clover coverage report -
Coverage timestamp: Thu Jul 5 2007 20:02:32 EDT
file stats: LOC: 288   Methods: 12
NCLOC: 221   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
JDBCCacheLoader.java 96.4% 90.5% 100% 92.6%
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.apache.commons.logging.LogFactory;
 6    import org.jboss.cache.Fqn;
 7    import org.jboss.cache.config.CacheLoaderConfig;
 8    import org.jboss.cache.config.CacheLoaderConfig.IndividualCacheLoaderConfig;
 9    import org.jboss.cache.marshall.NodeData;
 10   
 11    import java.io.InputStream;
 12    import java.io.ObjectInputStream;
 13    import java.sql.Connection;
 14    import java.sql.PreparedStatement;
 15    import java.sql.ResultSet;
 16    import java.sql.SQLException;
 17    import java.util.HashMap;
 18    import java.util.List;
 19    import java.util.Map;
 20   
 21    /**
 22    * JDBC implementation of <tt>AdjListJDBCCacheLoader</tt>.
 23    * Represents a faster alternative than JDBCCacheLoaderOld and relies on the same database structrure.
 24    * It is backward compatible with data created by existing <tt>JDBCCacheLoaderOld</tt> implemetation.
 25    * All configuration elements described there {@link org.jboss.cache.loader.JDBCCacheLoaderOld} also apply for this
 26    * implementation.
 27    * <p/>
 28    * <p/>
 29    * Additional configuration info: <br>
 30    * cache.jdbc.sql-concat : DBMS specific function for concat strings. Most likely this will be concat(1,2), but might
 31    * be different for proprietary systems.
 32    *
 33    * @author Mircea.Markus@iquestint.com
 34    * @author <a href="mailto:galder.zamarreno@jboss.com">Galder Zamarreno</a>
 35    * @version 1.0
 36    */
 37    @ThreadSafe
 38    public class JDBCCacheLoader extends AdjListJDBCCacheLoader
 39    {
 40   
 41    private static final Log log = LogFactory.getLog(JDBCCacheLoader.class);
 42   
 43    private JDBCCacheLoaderConfig config;
 44   
 45    /**
 46    * Builds a AdjListJDBCCacheLoaderConfig based on the supplied base config.
 47    */
 48  253 protected AdjListJDBCCacheLoaderConfig processConfig(CacheLoaderConfig.IndividualCacheLoaderConfig base)
 49    {
 50  253 if (base instanceof JDBCCacheLoaderConfig)
 51    {
 52  0 config = (JDBCCacheLoaderConfig) base;
 53    }
 54    else
 55    {
 56  253 config = new JDBCCacheLoaderConfig(base);
 57    }
 58  253 return config;
 59    }
 60   
 61    /**
 62    * As per interface's contract.
 63    * Performance Note: Optimised O(nodeDepth) db calls.
 64    */
 65  6539 public Object put(Fqn name, Object key, Object value) throws Exception
 66    {
 67  6539 Map toAdd = new HashMap();
 68  6539 toAdd.put(key, value);
 69  6539 Map existing = _put(name, toAdd);
 70  6539 return existing == null ? null : existing.get(key);
 71    }
 72   
 73    /**
 74    * As per interface's contract.
 75    * Performance Note: Optimised O(nodeDepth) db calls.
 76    */
 77  6383 public void put(Fqn name, Map attributes) throws Exception
 78    {
 79  6383 _put(name, attributes);
 80    }
 81   
 82    /**
 83    * As per interface's contrect.
 84    * Performance Note: O(1) db calls.
 85    */
 86  6636 public void remove(Fqn fqn) throws Exception
 87    {
 88  6636 Connection conn = null;
 89  6636 PreparedStatement ps = null;
 90  6636 try
 91    {
 92  6636 conn = cf.getConnection();
 93  6636 ps = conn.prepareStatement(config.getDeleteNodeSql());
 94    //apend / at the end avoids this issue: 'a/b/cd' is not a child of 'a/b/c'
 95  6636 ps.setString(1, fqn.isRoot() ? fqn.toString() : fqn + Fqn.SEPARATOR);
 96  6636 lock.acquireLock(fqn, true);
 97  6636 ps.executeUpdate();
 98    }
 99    catch (SQLException e)
 100    {
 101  0 log.error("Failed to remove the node : " + fqn, e);
 102  0 throw new IllegalStateException("Failure while removing sub-tree (" + fqn + ")" + e.getMessage());
 103    }
 104    finally
 105    {
 106  6636 safeClose(ps);
 107  6636 cf.close(conn);
 108  6636 lock.releaseLock(fqn);
 109    }
 110    }
 111   
 112   
 113    /**
 114    * Subscribes to contract.
 115    * Performance Note: O(2) db calls.
 116    */
 117  26 protected void getNodeDataList(Fqn fqn, List<NodeData> list) throws Exception
 118    {
 119  26 Map nodeAttributes = loadNode(fqn);
 120  26 if (nodeAttributes == null)
 121    {
 122  10 return;
 123    }
 124  16 Connection connection = null;
 125  16 PreparedStatement ps = null;
 126  16 ResultSet rs = null;
 127  16 try
 128    {
 129  16 connection = cf.getConnection();
 130  16 ps = connection.prepareStatement(config.getRecursiveChildrenSql());
 131  16 ps.setString(1, fqn.isRoot() ? fqn.toString() : fqn.toString() + Fqn.SEPARATOR);
 132  16 rs = ps.executeQuery();
 133  16 while (rs.next())
 134    {
 135  49 Map<Object, Object> attributes = readAttributes(rs, 2);
 136  49 Fqn path = Fqn.fromString(rs.getString(1));
 137  49 NodeData nodeData = (attributes == null || attributes.isEmpty()) ? new NodeData(path) : new NodeData(path, attributes);
 138  49 list.add(nodeData);
 139    }
 140    }
 141    catch (SQLException e)
 142    {
 143  0 log.error("Failed to load state for node(" + fqn + ") :" + e.getMessage(), e);
 144  0 throw new IllegalStateException("Failed to load state for node(" + fqn + ") :" + e.getMessage());
 145    }
 146    finally
 147    {
 148  16 safeClose(rs);
 149  16 safeClose(ps);
 150  16 cf.close(connection);
 151    }
 152    }
 153   
 154  49 private Map<Object, Object> readAttributes(ResultSet rs, int index) throws SQLException
 155    {
 156  49 Map<Object, Object> result;
 157  49 InputStream is = rs.getBinaryStream(index);
 158  49 if (is != null && !rs.wasNull())
 159    {
 160  32 ObjectInputStream ois;
 161  32 try
 162    {
 163  32 Object marshalledNode = unmarshall(is);
 164  32 result = (Map<Object, Object>) marshalledNode;
 165    }
 166    catch (Exception e)
 167    {
 168  0 log.error("Failure while reading attribute set from db", e);
 169  0 throw new SQLException("Failure while reading attribute set from db " + e);
 170    }
 171    }
 172    else
 173    {
 174  17 result = null;
 175    }
 176  49 return result;
 177    }
 178   
 179  12922 private Map _put(Fqn name, Map attributes) throws Exception
 180    {
 181  12922 lock.acquireLock(name, true);
 182  12922 try
 183    {
 184  12922 Map result = null;
 185  12922 Map treeNode = loadNode(name);
 186  12922 if (treeNode == null)
 187    {
 188  4731 addNewSubtree(name, attributes);
 189    }
 190  8191 else if (treeNode == NULL_NODE_IN_ROW)
 191    {
 192  19 updateNode(name, attributes);
 193    }
 194    else
 195    {//the node exists and the attribute map is NOT null
 196  8172 Map<Object, Object> newAttributes = new HashMap<Object, Object>(treeNode);
 197  8172 newAttributes.putAll(attributes);//creation sequnce important - we need to overwrite old values
 198  8172 updateNode(name, newAttributes);
 199  8172 result = treeNode;
 200    }
 201  12922 return result;
 202    }
 203    finally
 204    {
 205  12922 lock.releaseLock(name);
 206    }
 207    }
 208   
 209  4731 private void addNewSubtree(Fqn name, Map attributes) throws Exception
 210    {
 211  4731 Fqn currentNode = name;
 212  4731 do
 213    {
 214  5260 if (currentNode.equals(name))
 215    {
 216  4731 insertNode(currentNode, attributes);
 217    }
 218    else
 219    {
 220  529 insertNode(currentNode, null);
 221    }
 222  5260 if (currentNode.isRoot())
 223    {
 224  228 break;
 225    }
 226  5032 currentNode = currentNode.getParent();
 227    }
 228  5032 while (!exists(currentNode));
 229    }
 230   
 231   
 232  55550 protected Log getLogger()
 233    {
 234  55550 return log;
 235    }
 236   
 237   
 238    /**
 239    * Start is overwritten for the sake of backward compatibility only.
 240    * Here is the issue: old implementation does not create a Fqn.ROOT if not specifically told so.
 241    * As per put's contract, when calling put('/a/b/c', 'key', 'value') all parent nodes should be created up to root.
 242    * Root is not created, though. The compatibility problem comes in the case of loade ENTIRE state.
 243    * The implementation checks node's existence firstly, and based on that continues or not. As root is not
 244    * persisted nothing is loaded etc.
 245    */
 246  247 public void start() throws Exception
 247    {
 248  247 super.start();
 249  247 if (!exists(Fqn.ROOT) && getNodeCount() > 0)
 250    {
 251  2 put(Fqn.ROOT, null);
 252    }
 253    }
 254   
 255    /**
 256    * Returns a number representing the count of persisted children.
 257    */
 258  246 public int getNodeCount() throws Exception
 259    {
 260  246 Connection conn = null;
 261  246 PreparedStatement ps = null;
 262  246 ResultSet rs = null;
 263  246 try
 264    {
 265  246 conn = cf.getConnection();
 266  246 ps = conn.prepareStatement(config.getNodeCountSql());
 267  246 rs = ps.executeQuery();
 268  246 rs.next();//count(*) will always return one row
 269  246 return rs.getInt(1);
 270    }
 271    catch (Exception e)
 272    {
 273  0 log.error("Failure while trying to get the count of persisted nodes: " + e.getMessage(), e);
 274  0 throw new IllegalStateException("Failure while trying to get the count of persisted nodes: " + e.getMessage());
 275    }
 276    finally
 277    {
 278  246 safeClose(rs);
 279  246 safeClose(ps);
 280  246 cf.close(conn);
 281    }
 282    }
 283   
 284  247 public IndividualCacheLoaderConfig getConfig()
 285    {
 286  247 return config;
 287    }
 288    }