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.Iterator; 007 import java.util.List; 008 import java.util.Map; 009 010 import com.mockrunner.mock.jdbc.MockResultSet; 011 import com.mockrunner.util.common.ArrayUtil; 012 013 /** 014 * Abstract base class for all statement types 015 * that support parameters, i.e. <code>PreparedStatement</code> 016 * and <code>CallableStatement</code>. 017 */ 018 public abstract class AbstractParameterResultSetHandler extends AbstractResultSetHandler 019 { 020 private boolean exactMatchParameter = false; 021 private Map resultSetsForStatement = new HashMap(); 022 private Map updateCountForStatement = new HashMap(); 023 private Map throwsSQLException = new HashMap(); 024 private Map executedStatementParameters = new HashMap(); 025 026 /** 027 * Collects all SQL strings that were executed. 028 * @param sql the SQL string 029 * @param parameters a copy of the corresponding parameter map 030 */ 031 public void addParameterMapForExecutedStatement(String sql, Map parameters) 032 { 033 if(null != parameters) 034 { 035 if(null == executedStatementParameters.get(sql)) 036 { 037 executedStatementParameters.put(sql, new ParameterSets(sql)); 038 } 039 ParameterSets sets = (ParameterSets)executedStatementParameters.get(sql); 040 sets.addParameterSet(parameters); 041 } 042 } 043 044 /** 045 * Returns the <code>ParameterSets</code> for a specified 046 * SQL string. 047 * @param sql the SQL string 048 * @return the <code>Map</code> of parameters 049 */ 050 public ParameterSets getParametersForExecutedStatement(String sql) 051 { 052 return (ParameterSets)executedStatementParameters.get(sql); 053 } 054 055 /** 056 * Returns the <code>Map</code> of executed SQL strings. 057 * Each string maps to the corresponding {@link ParameterSets} 058 * object. 059 * @return the <code>Map</code> of parameters 060 */ 061 public Map getExecutedStatementParameter() 062 { 063 return Collections.unmodifiableMap(executedStatementParameters); 064 } 065 066 /** 067 * Sets if the specified parameters must match exactly 068 * in order and number. 069 * Defaults to <code>false</code>, i.e. the specified 070 * parameters must be present in the actual parameter 071 * list of the prepared statement with the correct index 072 * but it's ok if there are more actual parameters. 073 * @param exactMatchParameter must parameters match exactly 074 */ 075 public void setExactMatchParameter(boolean exactMatchParameter) 076 { 077 this.exactMatchParameter = exactMatchParameter; 078 } 079 080 /** 081 * Returns the first update count that matches the 082 * specified SQL string and the specified parameters. 083 * Please note that you can modify the match parameters with 084 * {@link #setCaseSensitive}, {@link #setExactMatch} and 085 * {@link #setUseRegularExpressions} and the match parameters for the 086 * specified parameter list with {@link #setExactMatchParameter}. 087 * @param sql the SQL string 088 * @param parameters the parameters 089 * @return the corresponding update count 090 */ 091 public Integer getUpdateCount(String sql, Map parameters) 092 { 093 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 094 List list = matcher.getMatchingObjects(updateCountForStatement, sql, true, true); 095 for(int ii = 0; ii < list.size(); ii++) 096 { 097 MockUpdateCountWrapper wrapper = (MockUpdateCountWrapper)list.get(ii); 098 if(doParameterMatch(wrapper.getParamters(), parameters)) 099 { 100 return wrapper.getUpdateCount(); 101 } 102 } 103 return null; 104 } 105 106 /** 107 * Returns the first <code>ResultSet</code> that matches the 108 * specified SQL string and the specified parameters. 109 * Please note that you can modify the match parameters with 110 * {@link #setCaseSensitive}, {@link #setExactMatch} and 111 * {@link #setUseRegularExpressions} and the match parameters for the 112 * specified parameter list with {@link #setExactMatchParameter}. 113 * @param sql the SQL string 114 * @param parameters the parameters 115 * @return the corresponding {@link MockResultSet} 116 */ 117 public MockResultSet getResultSet(String sql, Map parameters) 118 { 119 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 120 List list = matcher.getMatchingObjects(resultSetsForStatement, sql, true, true); 121 for(int ii = 0; ii < list.size(); ii++) 122 { 123 MockResultSetWrapper wrapper = (MockResultSetWrapper)list.get(ii); 124 if(doParameterMatch(wrapper.getParamters(), parameters)) 125 { 126 return wrapper.getResultSet(); 127 } 128 } 129 return null; 130 } 131 132 /** 133 * Returns if the specified SQL string with the specified parameters 134 * should raise an exception. 135 * This can be used to simulate database exceptions. 136 * Please note that you can modify the match parameters with 137 * {@link #setCaseSensitive}, {@link #setExactMatch} and 138 * {@link #setUseRegularExpressions} and the match parameters for the 139 * specified parameter list with {@link #setExactMatchParameter}. 140 * @param sql the SQL string 141 * @param parameters the parameters 142 * @return <code>true</code> if the specified SQL string should raise an exception, 143 * <code>false</code> otherwise 144 */ 145 public boolean getThrowsSQLException(String sql, Map parameters) 146 { 147 SQLStatementMatcher matcher = new SQLStatementMatcher(getCaseSensitive(), getExactMatch(), getUseRegularExpressions()); 148 List list = matcher.getMatchingObjects(throwsSQLException, sql, true, true); 149 for(int ii = 0; ii < list.size(); ii++) 150 { 151 if(doParameterMatch((Map)list.get(ii), parameters)) 152 { 153 return true; 154 } 155 } 156 return false; 157 } 158 159 protected boolean doParameterMatch(Map expectedParameters, Map actualParameters) 160 { 161 if(exactMatchParameter) 162 { 163 if(actualParameters.size() != expectedParameters.size()) return false; 164 Iterator iterator = actualParameters.keySet().iterator(); 165 while(iterator.hasNext()) 166 { 167 Object currentKey = iterator.next(); 168 Object expectedObject = expectedParameters.get(currentKey); 169 if(null == expectedObject) return false; 170 if(!ParameterUtil.compareParameter(actualParameters.get(currentKey), expectedObject)) 171 { 172 return false; 173 } 174 } 175 return true; 176 } 177 else 178 { 179 Iterator iterator = expectedParameters.keySet().iterator(); 180 while(iterator.hasNext()) 181 { 182 Object currentKey = iterator.next(); 183 Object actualObject = actualParameters.get(currentKey); 184 if(null == actualObject) return false; 185 if(!ParameterUtil.compareParameter(actualObject, expectedParameters.get(currentKey))) 186 { 187 return false; 188 } 189 } 190 return true; 191 } 192 } 193 194 /** 195 * Clears the <code>ResultSet</code> objects. 196 */ 197 public void clearResultSets() 198 { 199 super.clearResultSets(); 200 resultSetsForStatement.clear(); 201 } 202 203 /** 204 * Clears the update counts. 205 */ 206 public void clearUpdateCounts() 207 { 208 super.clearUpdateCounts(); 209 updateCountForStatement.clear(); 210 } 211 212 /** 213 * Clears the list of statements that should throw an exception 214 */ 215 public void clearThrowsSQLException() 216 { 217 super.clearThrowsSQLException(); 218 throwsSQLException.clear(); 219 } 220 221 /** 222 * Prepare a <code>ResultSet</code> for a specified SQL string and 223 * the specified parameters. The specified parameters array 224 * must contain the parameters in the correct order starting with 0 as 225 * the first parameter. Please keep in mind that parameters in 226 * <code>PreparedStatement</code> objects start with 1 as the first 227 * parameter. So <code>parameters[0]</code> maps to the 228 * parameter with index 1. 229 * Please note that you can modify the match parameters with 230 * {@link #setCaseSensitive}, {@link #setExactMatch} and 231 * {@link #setUseRegularExpressions} and the match parameters for the 232 * specified parameter list with {@link #setExactMatchParameter}. 233 * @param sql the SQL string 234 * @param resultSet the corresponding {@link MockResultSet} 235 * @param parameters the parameters 236 */ 237 public void prepareResultSet(String sql, MockResultSet resultSet, Object[] parameters) 238 { 239 prepareResultSet(sql, resultSet, ArrayUtil.getListFromObjectArray(parameters)); 240 } 241 242 /** 243 * Prepare a <code>ResultSet</code> for a specified SQL string and 244 * the specified parameters. The specified parameters <code>List</code> 245 * must contain the parameters in the correct order starting with 0 as 246 * the first parameter. Please keep in mind that parameters in 247 * <code>PreparedStatement</code> objects start with 1 as the first 248 * parameter. So <code>parameters.get(0)</code> maps to the 249 * parameter with index 1. 250 * Please note that you can modify the match parameters with 251 * {@link #setCaseSensitive}, {@link #setExactMatch} and 252 * {@link #setUseRegularExpressions} and the match parameters for the 253 * specified parameter list with {@link #setExactMatchParameter}. 254 * @param sql the SQL string 255 * @param resultSet the corresponding {@link MockResultSet} 256 * @param parameters the parameters 257 */ 258 public void prepareResultSet(String sql, MockResultSet resultSet, List parameters) 259 { 260 Map params = new HashMap(); 261 for(int ii = 0; ii < parameters.size(); ii++) 262 { 263 params.put(new Integer(ii + 1), parameters.get(ii)); 264 } 265 prepareResultSet(sql, resultSet, params); 266 } 267 268 /** 269 * Prepare a <code>ResultSet</code> for a specified SQL string and 270 * the specified parameters. The specified parameters <code>Map</code> 271 * must contain the parameters by mapping <code>Integer</code> objects 272 * to the corresponding parameter. The <code>Integer</code> object 273 * is the index of the parameter. In the case of a <code>CallableStatement</code> 274 * there are also allowed <code>String</code> keys for named parameters. 275 * Please note that you can modify the match parameters with 276 * {@link #setCaseSensitive}, {@link #setExactMatch} and 277 * {@link #setUseRegularExpressions} and the match parameters for the 278 * specified parameter list with {@link #setExactMatchParameter}. 279 * @param sql the SQL string 280 * @param resultSet the corresponding {@link MockResultSet} 281 * @param parameters the parameters 282 */ 283 public void prepareResultSet(String sql, MockResultSet resultSet, Map parameters) 284 { 285 List list = (List)resultSetsForStatement.get(sql); 286 if(null == list) 287 { 288 list = new ArrayList(); 289 resultSetsForStatement.put(sql, list); 290 } 291 list.add(new MockResultSetWrapper(resultSet, parameters)); 292 } 293 294 /** 295 * Prepare if the specified SQL string with the specified parameters 296 * should raise an exception. 297 * This can be used to simulate database exceptions. 298 * The specified parameters array must contain the parameters in 299 * the correct order starting with 0 as the first parameter. 300 * Please keep in mind that parameters in <code>PreparedStatement</code> 301 * objects start with 1 as the first parameter. So <code>parameters[0]</code> 302 * maps to the parameter with index 1. 303 * Please note that you can modify the match parameters with 304 * {@link #setCaseSensitive}, {@link #setExactMatch} and 305 * {@link #setUseRegularExpressions} and the match parameters for the 306 * specified parameter list with {@link #setExactMatchParameter}. 307 * @param sql the SQL string 308 * @param parameters the parameters 309 */ 310 public void prepareThrowsSQLException(String sql, Object[] parameters) 311 { 312 prepareThrowsSQLException(sql, ArrayUtil.getListFromObjectArray(parameters)); 313 } 314 315 /** 316 * Prepare if the specified SQL string with the specified parameters 317 * should raise an exception. 318 * This can be used to simulate database exceptions. 319 * The specified parameters <code>List</code> must contain the 320 * parameters in the correct order starting with 0 as the first 321 * parameter. Please keep in mind that parameters in 322 * <code>PreparedStatement</code> objects start with 1 as the first 323 * parameter. So <code>parameters.get(0)</code> maps to the parameter 324 * with index 1. 325 * Please note that you can modify the match parameters with 326 * {@link #setCaseSensitive}, {@link #setExactMatch} and 327 * {@link #setUseRegularExpressions} and the match parameters for the 328 * specified parameter list with {@link #setExactMatchParameter}. 329 * @param sql the SQL string 330 * @param parameters the parameters 331 */ 332 public void prepareThrowsSQLException(String sql, List parameters) 333 { 334 Map params = new HashMap(); 335 for(int ii = 0; ii < parameters.size(); ii++) 336 { 337 params.put(new Integer(ii + 1), parameters.get(ii)); 338 } 339 prepareThrowsSQLException(sql, params); 340 } 341 342 /** 343 * Prepare if the specified SQL string with the specified parameters 344 * should raise an exception. 345 * This can be used to simulate database exceptions. 346 * Please note that you can modify the match parameters with 347 * {@link #setCaseSensitive}, {@link #setExactMatch} and 348 * {@link #setUseRegularExpressions} and the match parameters for the 349 * specified parameter list with {@link #setExactMatchParameter}. 350 * @param sql the SQL string 351 * @param parameters the parameters 352 */ 353 public void prepareThrowsSQLException(String sql, Map parameters) 354 { 355 List list = (List)throwsSQLException.get(sql); 356 if(null == list) 357 { 358 list = new ArrayList(); 359 throwsSQLException.put(sql, list); 360 } 361 list.add(parameters); 362 } 363 364 /** 365 * Prepare the update count for execute update calls for a specified SQL string 366 * and the specified parameters. The specified parameters array 367 * must contain the parameters in the correct order starting with 0 as 368 * the first parameter. Please keep in mind that parameters in 369 * <code>PreparedStatement</code> objects start with 1 as the first 370 * parameter. So <code>parameters[0]</code> maps to the 371 * parameter with index 1. 372 * Please note that you can modify the match parameters with 373 * {@link #setCaseSensitive}, {@link #setExactMatch} and 374 * {@link #setUseRegularExpressions} and the match parameters for the 375 * specified parameter list with {@link #setExactMatchParameter}. 376 * @param sql the SQL string 377 * @param updateCount the update count 378 * @param parameters the parameters 379 */ 380 public void prepareUpdateCount(String sql, int updateCount, Object[] parameters) 381 { 382 prepareUpdateCount(sql, updateCount, ArrayUtil.getListFromObjectArray(parameters)); 383 } 384 385 /** 386 * Prepare the update count for execute update calls for a specified SQL string 387 * and the specified parameters. The specified parameters <code>List</code> 388 * must contain the parameters in the correct order starting with 0 as 389 * the first parameter. Please keep in mind that parameters in 390 * <code>PreparedStatement</code> objects start with 1 as the first 391 * parameter. So <code>parameters.get(0)</code> maps to the 392 * parameter with index 1. 393 * Please note that you can modify the match parameters with 394 * {@link #setCaseSensitive}, {@link #setExactMatch} and 395 * {@link #setUseRegularExpressions} and the match parameters for the 396 * specified parameter list with {@link #setExactMatchParameter}. 397 * @param sql the SQL string 398 * @param updateCount the update count 399 * @param parameters the parameters 400 */ 401 public void prepareUpdateCount(String sql, int updateCount, List parameters) 402 { 403 Map params = new HashMap(); 404 for(int ii = 0; ii < parameters.size(); ii++) 405 { 406 params.put(new Integer(ii + 1), parameters.get(ii)); 407 } 408 prepareUpdateCount(sql, updateCount, params); 409 } 410 411 /** 412 * Prepare the update count for execute update calls for a specified SQL string 413 * and the specified parameters. The specified parameters <code>Map</code> 414 * must contain the parameters by mapping <code>Integer</code> objects 415 * to the corresponding parameter. The <code>Integer</code> object 416 * is the index of the parameter. In the case of a <code>CallableStatement</code> 417 * there are also allowed <code>String</code> keys for named parameters. 418 * Please note that you can modify the match parameters with 419 * {@link #setCaseSensitive}, {@link #setExactMatch} and 420 * {@link #setUseRegularExpressions} and the match parameters for the 421 * specified parameter list with {@link #setExactMatchParameter}. 422 * @param sql the SQL string 423 * @param updateCount the update count 424 * @param parameters the parameters 425 */ 426 public void prepareUpdateCount(String sql, int updateCount, Map parameters) 427 { 428 List list = (List)updateCountForStatement.get(sql); 429 if(null == list) 430 { 431 list = new ArrayList(); 432 updateCountForStatement.put(sql, list); 433 } 434 list.add(new MockUpdateCountWrapper(updateCount, parameters)); 435 } 436 437 private class MockResultSetWrapper 438 { 439 private MockResultSet resultSet; 440 private Map parameters; 441 442 public MockResultSetWrapper(MockResultSet resultSet, Map parameters) 443 { 444 this.resultSet = resultSet; 445 this.parameters = parameters; 446 } 447 448 public Map getParamters() 449 { 450 return parameters; 451 } 452 453 public MockResultSet getResultSet() 454 { 455 return resultSet; 456 } 457 } 458 459 private class MockUpdateCountWrapper 460 { 461 private Integer updateCount; 462 private Map parameters; 463 464 public MockUpdateCountWrapper(int updateCount, Map parameters) 465 { 466 this.updateCount = new Integer(updateCount); 467 this.parameters = parameters; 468 } 469 470 public Map getParamters() 471 { 472 return parameters; 473 } 474 475 public Integer getUpdateCount() 476 { 477 return updateCount; 478 } 479 } 480 }