001 package com.mockrunner.jdbc; 002 003 import java.util.ArrayList; 004 import java.util.Collections; 005 import java.util.HashMap; 006 import java.util.List; 007 import java.util.Map; 008 009 import com.mockrunner.mock.jdbc.MockResultSet; 010 011 /** 012 * Abstract base class for all <code>ResultSet</code> handlers. 013 * Used to coordinate <code>ResultSet</code> objects for a 014 * statement. You can use this class to prepare <code>ResultSet</code> 015 * objects and update count values that are returned by the 016 * <code>execute</code> method of a statement, if the current 017 * SQL string matches. 018 * Furthermore it can be used to create <code>ResultSet</code> objects. 019 * Please note that the <code>ResultSet</code> objects you create and 020 * prepare with this handler are cloned when executing statements. 021 * So you cannot rely on object identity. You have to use the id 022 * of the <code>ResultSet</code> to identify it. 023 * The <code>ResultSet</code> objects returned by {@link #getReturnedResultSets} 024 * are actually the instances the executed statements returned. 025 */ 026 public abstract class AbstractResultSetHandler 027 { 028 private boolean caseSensitive = false; 029 private boolean exactMatch = false; 030 private boolean useRegularExpressions = false; 031 private MockResultSet globalResultSet; 032 private Map resultSetsForStatement = new HashMap(); 033 private int globalUpdateCount = 0; 034 private Map updateCountForStatement = new HashMap(); 035 private Map returnsResultSetMap = new HashMap(); 036 private List throwsSQLException = new ArrayList(); 037 private List executedStatements = new ArrayList(); 038 private List returnedResultSets = new ArrayList(); 039 040 /** 041 * Creates a new <code>ResultSet</code> with a 042 * random id. 043 * @return the new <code>ResultSet</code> 044 */ 045 public MockResultSet createResultSet() 046 { 047 return new MockResultSet(String.valueOf(Math.random())); 048 } 049 050 /** 051 * Creates a new <code>ResultSet</code> with the specified id. 052 * @param id the id 053 * @return the new <code>ResultSet</code> 054 */ 055 public MockResultSet createResultSet(String id) 056 { 057 return new MockResultSet(id); 058 } 059 060 /** 061 * Returns a new <code>ResultSet</code> created by 062 * the specified factory. Currently there's only 063 * the {@link FileResultSetFactory} to create <code>ResultSet</code> 064 * objects based on CSV files but you can implement your own factories. 065 * @param factory the {@link ResultSetFactory} 066 * @return the new <code>ResultSet</code> 067 */ 068 public MockResultSet createResultSet(ResultSetFactory factory) 069 { 070 return factory.create(String.valueOf(Math.random())); 071 } 072 073 /** 074 * Returns a new <code>ResultSet</code> created by 075 * the specified factory. Currently there's only 076 * the {@link FileResultSetFactory} to create <code>ResultSet</code> 077 * objects based on CSV files but you can implement your own factories. 078 * Uses a random id. 079 * @param id the id 080 * @param factory the {@link ResultSetFactory} 081 * @return the new <code>ResultSet</code> 082 */ 083 public MockResultSet createResultSet(String id, ResultSetFactory factory) 084 { 085 return factory.create(id); 086 } 087 088 /** 089 * Set if specified SQL strings should be handled case sensitive. 090 * Defaults to to <code>false</code>, i.e. <i>INSERT</i> is the same 091 * as <i>insert</i>. 092 * Please note that this method controls SQL statement 093 * matching for prepared results and update counts, i.e. what 094 * statements the tested application has to execute to receive 095 * a specified result. Unlike {@link JDBCTestModule#setCaseSensitive(boolean)} 096 * it does not control the statement matching of {@link JDBCTestModule} 097 * methods. 098 * @param caseSensitive enable or disable case sensitivity 099 */ 100 public void setCaseSensitive(boolean caseSensitive) 101 { 102 this.caseSensitive = caseSensitive; 103 } 104 105 /** 106 * Set if specified SQL statements must match exactly. 107 * Defaults to <code>false</code>, i.e. the SQL string 108 * does not need to match exactly. If the original statement 109 * is <i>insert into mytable values(?, ?, ?)</i> 110 * the string <i>insert into mytable</i> will match this statement. 111 * Usually <code>false</code> is the best choice, so 112 * prepared <code>ResultSet</code> objects do not have 113 * to match exactly the current statements SQL string. 114 * Please note that this method controls SQL statement 115 * matching for prepared results and update counts, i.e. what 116 * statements the tested application has to execute to receive 117 * a specified result. Unlike {@link JDBCTestModule#setExactMatch(boolean)} 118 * it does not control the statement matching of {@link JDBCTestModule} 119 * methods. 120 * @param exactMatch enable or disable exact matching 121 */ 122 public void setExactMatch(boolean exactMatch) 123 { 124 this.exactMatch = exactMatch; 125 } 126 127 /** 128 * Set if regular expressions should be used when matching 129 * SQL statements. Irrelevant if <code>exactMatch</code> is 130 * <code>true</code>. Default is <code>false</code>, i.e. you 131 * cannot use regular expressions and matching is based 132 * on string comparison (which is much faster). Enable 133 * this feature only if necessary. 134 * Please note that this method controls SQL statement 135 * matching for prepared results and update counts, i.e. what 136 * statements the tested application has to execute to receive 137 * a specified result. Unlike {@link JDBCTestModule#setUseRegularExpressions(boolean)} 138 * it does not control the statement matching of {@link JDBCTestModule} 139 * methods. 140 * @param useRegularExpressions should regular expressions be used 141 */ 142 public void setUseRegularExpressions(boolean useRegularExpressions) 143 { 144 this.useRegularExpressions = useRegularExpressions; 145 } 146 147 /** 148 * Collects all SQL strings that were executed. 149 * @param sql the SQL string 150 */ 151 public void addExecutedStatement(String sql) 152 { 153 executedStatements.add(sql); 154 } 155 156 /** 157 * Collects all <code>ResultSet</code> objects that were returned by 158 * a <code>Statement</code>, <code>PreparedStatement</code> or 159 * <code>CallableStatement</code>. 160 * @param resultSet the <code>ResultSet</code> 161 */ 162 public void addReturnedResultSet(MockResultSet resultSet) 163 { 164 if(null == resultSet) return; 165 returnedResultSets.add(resultSet); 166 } 167 168 /** 169 * Returns the <code>List</code> of all executed SQL strings. 170 * @return the <code>List</code> of executed SQL strings 171 */ 172 public List getExecutedStatements() 173 { 174 return Collections.unmodifiableList(executedStatements); 175 } 176 177 /** 178 * Returns the <code>List</code> of all returned <code>ResultSet</code> objects. 179 * @return the <code>List</code> of returned <code>ResultSet</code> objects 180 */ 181 public List getReturnedResultSets() 182 { 183 return Collections.unmodifiableList(returnedResultSets); 184 } 185 186 /** 187 * Clears the <code>ResultSet</code> objects. 188 */ 189 public void clearResultSets() 190 { 191 resultSetsForStatement.clear(); 192 } 193 194 /** 195 * Clears the update counts. 196 */ 197 public void clearUpdateCounts() 198 { 199 updateCountForStatement.clear(); 200 } 201 202 /** 203 * Clears the definitions if statements return 204 * <code>ResultSet</code> objects or update counts. 205 */ 206 public void clearReturnsResultSet() 207 { 208 returnsResultSetMap.clear(); 209 } 210 211 /** 212 * Clears the list of statements that should throw an exception. 213 */ 214 public void clearThrowsSQLException() 215 { 216 throwsSQLException.clear(); 217 } 218 219 /** 220 * Returns the <code>Map</code> of all <code>ResultSet</code> 221 * objects, that were added with {@link #prepareResultSet(String, MockResultSet)}. 222 * The SQL strings map to the corresponding <code>ResultSet</code>. 223 * @return the <code>Map</code> of <code>ResultSet</code> objects 224 */ 225 public Map getResultSetMap() 226 { 227 return Collections.unmodifiableMap(resultSetsForStatement); 228 } 229 230 /** 231 * Returns the <code>Map</code> of all update counts, that were added 232 * with {@link #prepareUpdateCount(String, int)}. 233 * The SQL strings map to the corresponding update count as 234 * <code>Integer</code> object. 235 * @return the <code>Map</code> of <code>ResultSet</code> objects 236 */ 237 public Map getUpdateCountMap() 238 { 239 return Collections.unmodifiableMap(updateCountForStatement); 240 } 241 242 /** 243 * Returns the first <code>ResultSet</code> that matches the 244 * specified SQL string. Please note that you can modify 245 * the match parameters with {@link #setCaseSensitive}, 246 * {@link #setExactMatch} and {@link #setUseRegularExpressions}. 247 * @param sql the SQL string 248 * @return the corresponding {@link MockResultSet} 249 */ 250 public MockResultSet getResultSet(String sql) 251 { 252 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 253 List list = matcher.getMatchingObjects(resultSetsForStatement, sql, true, true); 254 if(null != list && list.size() > 0) 255 { 256 return (MockResultSet)list.get(0); 257 } 258 return null; 259 } 260 261 /** 262 * Returns the global <code>ResultSet</code>. 263 * The statement returns the global <code>ResultSet</code> 264 * if no <code>ResultSet</code> can be found for the current 265 * SQL string. 266 * @return the global {@link MockResultSet} 267 */ 268 public MockResultSet getGlobalResultSet() 269 { 270 return globalResultSet; 271 } 272 273 /** 274 * Returns the first update count that matches the 275 * specified SQL string. Please note that you can modify 276 * the match parameters with {@link #setCaseSensitive}, 277 * {@link #setExactMatch} and {@link #setUseRegularExpressions}. 278 * @param sql the SQL string 279 * @return the corresponding update count 280 */ 281 public Integer getUpdateCount(String sql) 282 { 283 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 284 List list = matcher.getMatchingObjects(updateCountForStatement, sql, true, true); 285 if(null != list && list.size() > 0) 286 { 287 return (Integer)list.get(0); 288 } 289 return null; 290 } 291 292 /** 293 * Returns the global update count for <code>executeUpdate</code> 294 * calls. 295 * The statement returns the global update count 296 * if no update count can be found for the current 297 * SQL string. 298 * @return the global update count 299 */ 300 public int getGlobalUpdateCount() 301 { 302 return globalUpdateCount; 303 } 304 305 /** 306 * Returns if the specified SQL string is a select that returns 307 * a <code>ResultSet</code>. 308 * Usually you do not have to specify this. 309 * It is assumed that an SQL string returns a <code>ResultSet</code> 310 * if it contains <i>SELECT</i>. 311 * Please note that you can modify the match parameters with 312 * {@link #setCaseSensitive}, {@link #setExactMatch} and 313 * {@link #setUseRegularExpressions}. 314 * @param sql the SQL string 315 * @return <code>true</code> if the SQL string returns a <code>ResultSet</code> 316 */ 317 public Boolean getReturnsResultSet(String sql) 318 { 319 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 320 List list = matcher.getMatchingObjects(returnsResultSetMap, sql, true, true); 321 if(null != list && list.size() > 0) 322 { 323 return (Boolean)list.get(0); 324 } 325 return null; 326 } 327 328 /** 329 * Returns if the specified SQL string should raise an exception. 330 * This can be used to simulate database exceptions. 331 * Please note that you can modify the match parameters with 332 * {@link #setCaseSensitive}, {@link #setExactMatch} and 333 * {@link #setUseRegularExpressions}. 334 * @param sql the SQL string 335 * @return <code>true</code> if the specified SQL string should raise an exception, 336 * <code>false</code> otherwise 337 */ 338 public boolean getThrowsSQLException(String sql) 339 { 340 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 341 if(matcher.contains(throwsSQLException, sql, true)) return true; 342 return false; 343 } 344 345 /** 346 * Prepare a <code>ResultSet</code> for a specified SQL string. 347 * Please note that you can modify the match parameters with 348 * {@link #setCaseSensitive}, {@link #setExactMatch} and 349 * {@link #setUseRegularExpressions}. 350 * @param sql the SQL string 351 * @param resultSet the corresponding {@link MockResultSet} 352 */ 353 public void prepareResultSet(String sql, MockResultSet resultSet) 354 { 355 resultSetsForStatement.put(sql, resultSet); 356 } 357 358 /** 359 * Prepare the global <code>ResultSet</code>. 360 * The statement returns the global <code>ResultSet</code> 361 * if no <code>ResultSet</code> can be found for the current 362 * SQL string. 363 * @param resultSet the {@link MockResultSet} 364 */ 365 public void prepareGlobalResultSet(MockResultSet resultSet) 366 { 367 this.globalResultSet = resultSet; 368 } 369 370 /** 371 * Prepare the update count for <code>executeUpdate</code> calls 372 * for a specified SQL string. Please note that you can modify 373 * the match parameters with {@link #setCaseSensitive}, 374 * {@link #setExactMatch} and {@link #setUseRegularExpressions}. 375 * @param sql the SQL string 376 * @param updateCount the update count 377 */ 378 public void prepareUpdateCount(String sql, int updateCount) 379 { 380 updateCountForStatement.put(sql, new Integer(updateCount)); 381 } 382 383 /** 384 * Prepare the global update count for <code>executeUpdate</code> calls. 385 * The statement returns the global update count 386 * if no update count can be found for the current 387 * SQL string. 388 * @param updateCount the update count 389 */ 390 public void prepareGlobalUpdateCount(int updateCount) 391 { 392 this.globalUpdateCount = updateCount; 393 } 394 395 /** 396 * Prepare if the specified SQL string is a select that returns 397 * a <code>ResultSet</code>. Usually you do not have to specify this. 398 * It is assumed that an SQL string returns a <code>ResultSet</code> 399 * if it contains <i>SELECT</i>. 400 * Please note that you can modify the match parameters with 401 * {@link #setCaseSensitive}, {@link #setExactMatch} and 402 * {@link #setUseRegularExpressions}. 403 * @param sql the SQL string 404 * @param returnsResultSet specify if the SQL string returns a <code>ResultSet</code> 405 */ 406 public void prepareReturnsResultSet(String sql, boolean returnsResultSet) 407 { 408 returnsResultSetMap.put(sql, new Boolean(returnsResultSet)); 409 } 410 411 /** 412 * Prepare if the specified SQL string should raise an exception. 413 * This can be used to simulate database exceptions. 414 * Please note that you can modify the match parameters with 415 * {@link #setCaseSensitive}, {@link #setExactMatch} and 416 * {@link #setUseRegularExpressions}. 417 * @param sql the SQL string 418 */ 419 public void prepareThrowsSQLException(String sql) 420 { 421 throwsSQLException.add(sql); 422 } 423 424 /** 425 * Clears the global <code>ResultSet</code>. 426 */ 427 public void clearGlobalResultSet() 428 { 429 this.globalResultSet = null; 430 } 431 432 /** 433 * Returns if specified SQL strings should be handled case sensitive. 434 * @return is case sensitivity enabled or disabled 435 */ 436 protected boolean getCaseSensitive() 437 { 438 return caseSensitive; 439 } 440 441 /** 442 * Returns if specified SQL statements must match exactly. 443 * @return is exact matching enabled or disabled 444 */ 445 protected boolean getExactMatch() 446 { 447 return exactMatch; 448 } 449 450 /** 451 * Returns if regular expression matching is enabled 452 * @return if regular expression matching is enabled 453 */ 454 protected boolean getUseRegularExpressions() 455 { 456 return useRegularExpressions; 457 } 458 }