001 package com.mockrunner.connector; 002 003 import java.util.ArrayList; 004 import java.util.List; 005 006 import javax.resource.ResourceException; 007 import javax.resource.cci.IndexedRecord; 008 import javax.resource.cci.InteractionSpec; 009 import javax.resource.cci.Record; 010 011 import com.mockrunner.base.NestedApplicationException; 012 import com.mockrunner.mock.connector.cci.MockIndexedRecord; 013 014 /** 015 * This interaction implementor works with indexed records. It takes a <code>List</code> for 016 * the request and a <code>List</code> or a <code>Record</code> instance for the response. 017 * If the request <code>List</code> is <code>null</code>, which is the default, 018 * the implementor accepts any request and returns the specified result. If a request 019 * <code>List</code> is specified, this implementor accepts only requests that contain the same 020 * data as the specified expected request <code>List</code>. The underlying lists are compared 021 * as described in the <code>List.equals</code> method. 022 * If a request is accepted, this implementor replies with the specified 023 * response. You can use the various constructors and <code>set</code> methods 024 * to configure the expected request and the response.<br> 025 * Please check out the documentation of the various methods for details. 026 */ 027 public class IndexedRecordInteraction implements InteractionImplementor 028 { 029 private boolean enabled; 030 private List expectedRequest; 031 private List responseData; 032 private Class responseClass; 033 private Record responseRecord; 034 035 /** 036 * Sets the expected request and the response to <code>null</code>, 037 * i.e. an empty response is returned for every request. 038 */ 039 public IndexedRecordInteraction() 040 { 041 this(null, null, MockIndexedRecord.class); 042 } 043 044 /** 045 * Sets the expected request to <code>null</code> and prepares 046 * the specified response <code>List</code>. The response class for the 047 * {@link #execute(InteractionSpec,Record)} method is set 048 * to the default {@link com.mockrunner.mock.connector.cci.MockIndexedRecord}. 049 * It is allowed to pass <code>null</code> for the response <code>List</code> 050 * which is equivalent to an empty response. 051 * The specified response is returned for every request. 052 * @param responseList the response <code>List</code> 053 */ 054 public IndexedRecordInteraction(List responseList) 055 { 056 this(null, responseList, MockIndexedRecord.class); 057 } 058 059 /** 060 * Sets the specified expected request <code>List</code> and prepares 061 * the specified response <code>List</code>. The response class for the 062 * {@link #execute(InteractionSpec,Record)} method is set 063 * to the default {@link com.mockrunner.mock.connector.cci.MockIndexedRecord}. 064 * It is allowed to pass <code>null</code> for the request and response <code>List</code> 065 * which is equivalent to an empty expected request (i.e. every request is accepted) 066 * or to an empty response respectively. 067 * The specified response is returned, if the actual request matches the specified expected 068 * request. 069 * @param expectedRequest the expected request <code>List</code 070 * @param responseList the response <code>List</code 071 */ 072 public IndexedRecordInteraction(List expectedRequest, List responseList) 073 { 074 this(expectedRequest, responseList, MockIndexedRecord.class); 075 } 076 077 /** 078 * Sets the expected request to <code>null</code> and prepares 079 * the specified response <code>List</code>. The response class for the 080 * {@link #execute(InteractionSpec,Record)} method is set 081 * to the specified <code>responseClass</code>. The specified 082 * <code>responseClass</code> must implement <code>IndexedRecord</code>, 083 * otherwise an <code>IllegalArgumentException</code> will be thrown. 084 * It is allowed to pass <code>null</code> for the response <code>List</code> 085 * which is equivalent to an empty response. 086 * The specified response is returned for every request. 087 * @param responseList the response <code>List</code 088 * @param responseClass the response <code>Record</code> class 089 * @throws IllegalArgumentException if the <code>responseClass</code> 090 * is not valid 091 */ 092 public IndexedRecordInteraction(List responseList, Class responseClass) 093 { 094 this(null, responseList, responseClass); 095 } 096 097 /** 098 * Sets the specified expected request <code>List</code> and prepares 099 * the specified response <code>List</code>. The response class for the 100 * {@link #execute(InteractionSpec,Record)} method is set 101 * to the specified <code>responseClass</code>. The specified 102 * <code>responseClass</code> must implement <code>IndexedRecord</code>, 103 * otherwise an <code>IllegalArgumentException</code> will be thrown. 104 * It is allowed to pass <code>null</code> for the request and response <code>List</code> 105 * which is equivalent to an empty expected request (i.e. every request is accepted) 106 * or to an empty response respectively. 107 * The specified response is returned, if the actual request matches the specified expected 108 * request. 109 * @param expectedRequest the expected request <code>List</code> 110 * @param responseList the response <code>List</code> 111 * @param responseClass the response <code>Record</code> class 112 * @throws IllegalArgumentException if the <code>responseClass</code> 113 * is not valid 114 */ 115 public IndexedRecordInteraction(List expectedRequest, List responseList, Class responseClass) 116 { 117 setExpectedRequest(expectedRequest); 118 setResponse(responseList, responseClass); 119 this.enabled = true; 120 } 121 122 /** 123 * Sets the specified expected request <code>List</code> and the response 124 * <code>Record</code> for the {@link #execute(InteractionSpec, Record)} 125 * method. The response <code>Record</code> is ignored for 126 * {@link #execute(InteractionSpec,Record,Record)} but takes precedence 127 * over the specified response <code>List</code> for {@link #execute(InteractionSpec, Record)}. 128 * It is allowed to pass <code>null</code> for the request and response <code>Record</code> 129 * which is equivalent to an empty expected request (i.e. every request is accepted) 130 * or to no specified response <code>Record</code>, i.e. the specified response 131 * <code>List</code> is taken. 132 * The specified response is returned, if the actual request matches the specified expected 133 * request. 134 * @param expectedRequest the expected request <code>List</code> 135 * @param responseRecord the response <code>Record</code> 136 */ 137 public IndexedRecordInteraction(List expectedRequest, Record responseRecord) 138 { 139 setExpectedRequest(expectedRequest); 140 setResponse(responseRecord); 141 this.enabled = true; 142 } 143 144 /** 145 * Sets the expected request to <code>null</code> and prepares the response 146 * <code>Record</code> for the {@link #execute(InteractionSpec, Record)} 147 * method. The response <code>Record</code> is ignored for 148 * {@link #execute(InteractionSpec,Record,Record)} but takes precedence 149 * over the specified response <code>List</code> for {@link #execute(InteractionSpec, Record)}. 150 * It is allowed to pass <code>null</code> for the response <code>Record</code> 151 * which is equivalent to no specified response <code>Record</code>, i.e. the specified response 152 * <code>List</code> is taken. 153 * The specified response is returned for every request. 154 * @param responseRecord the response <code>Record</code> 155 */ 156 public IndexedRecordInteraction(Record responseRecord) 157 { 158 this(null, responseRecord); 159 } 160 161 /** 162 * Enables this implementor. 163 */ 164 public void enable() 165 { 166 this.enabled = true; 167 } 168 169 /** 170 * Disables this implementor. {@link #canHandle(InteractionSpec, Record, Record)} 171 * always returns <code>false</code>, if this implementor is disabled. 172 */ 173 public void disable() 174 { 175 this.enabled = false; 176 } 177 178 /** 179 * Sets the specified expected request <code>List</code>. The response is returned, 180 * if the actual request matches the specified expected request <code>List</code> 181 * according to <code>List.equals</code>. 182 * It is allowed to pass <code>null</code> for the request <code>List</code> 183 * which is equivalent to an empty expected request (i.e. every request 184 * is accepted). 185 * @param expectedRequest the expected request <code>List</code> 186 */ 187 public void setExpectedRequest(List expectedRequest) 188 { 189 if(null == expectedRequest) 190 { 191 this.expectedRequest = null; 192 } 193 else 194 { 195 this.expectedRequest = new ArrayList(expectedRequest); 196 } 197 } 198 199 /** 200 * Prepares the specified response <code>List</code>. The response class for the 201 * {@link #execute(InteractionSpec,Record)} method is set 202 * to the default {@link com.mockrunner.mock.connector.cci.MockIndexedRecord}. 203 * It is allowed to pass <code>null</code> for the response <code>List</code> 204 * which is equivalent to an empty response. 205 * @param responseList the response <code>List</code 206 */ 207 public void setResponse(List responseList) 208 { 209 setResponse(responseList, MockIndexedRecord.class); 210 } 211 212 /** 213 * Prepares the specified response <code>List</code>. The response class for the 214 * {@link #execute(InteractionSpec,Record)} method is set 215 * to the specified <code>responseClass</code>. The specified 216 * <code>responseClass</code> must implement <code>IndexedRecord</code>, 217 * otherwise an <code>IllegalArgumentException</code> will be thrown. 218 * It is allowed to pass <code>null</code> for the response <code>List</code> 219 * which is equivalent to an empty response. 220 * @param responseList the response <code>List</code> 221 * @param responseClass the response <code>Record</code> class 222 * @throws IllegalArgumentException if the <code>responseClass</code> 223 * is not valid 224 */ 225 public void setResponse(List responseList, Class responseClass) 226 { 227 if(!isResponseClassAcceptable(responseClass)) 228 { 229 throw new IllegalArgumentException("responseClass must implement " + IndexedRecord.class.getName()); 230 } 231 if(null == responseList) 232 { 233 this.responseData = null; 234 } 235 else 236 { 237 this.responseData = new ArrayList(responseList); 238 } 239 this.responseClass = responseClass; 240 } 241 242 /** 243 * Prepares the response <code>Record</code> for the 244 * {@link #execute(InteractionSpec, Record)} method. The response 245 * <code>Record</code> is ignored for {@link #execute(InteractionSpec,Record,Record)} 246 * but takes precedence over the specified response <code>List</code> for 247 * {@link #execute(InteractionSpec, Record)}. 248 * It is allowed to pass <code>null</code> for the response <code>Record</code> 249 * which is equivalent to no specified response <code>Record</code>, i.e. the specified response 250 * <code>List</code> is taken. 251 * @param responseRecord the response <code>Record</code> 252 */ 253 public void setResponse(Record responseRecord) 254 { 255 this.responseRecord = responseRecord; 256 } 257 258 /** 259 * Returns <code>true</code> if this implementor is enabled and will handle the request. 260 * This method returns <code>true</code> if the following prerequisites are fulfilled:<br><br> 261 * It is enabled.<br><br> 262 * The response <code>Record</code> must implement <code>IndexedRecord</code> 263 * or it must be <code>null</code> (which is the case, if the actual request 264 * targets the {@link #execute(InteractionSpec,Record)} method instead of 265 * {@link #execute(InteractionSpec,Record,Record)}).<br><br> 266 * The expected request must be <code>null</code> (use the various 267 * <code>setExpectedRequest</code> methods) or the actual request <code>Record</code> 268 * must implement <code>IndexedRecord</code> and must contain the same data as 269 * the specified expected request <code>List</code> according to <code>List.equals</code>.<br><br> 270 * Otherwise, <code>false</code> is returned. 271 * @param interactionSpec the <code>InteractionSpec</code> for the actual call 272 * @param actualRequest the request for the actual call 273 * @param actualResponse the response for the actual call, may be <code>null</code> 274 * @return <code>true</code> if this implementor will handle the request and 275 * will return the specified response, <code>false</code> otherwise 276 */ 277 public boolean canHandle(InteractionSpec interactionSpec, Record actualRequest, Record actualResponse) 278 { 279 if(!enabled) return false; 280 if(!isResponseAcceptable(actualResponse)) return false; 281 return doesRequestMatch(actualRequest); 282 } 283 284 private boolean doesRequestMatch(Record request) 285 { 286 if(null == expectedRequest) return true; 287 if(null == request) return false; 288 if(request instanceof IndexedRecord) 289 { 290 try 291 { 292 IndexedRecord indexedRequest = (IndexedRecord)request; 293 if(indexedRequest.size() != expectedRequest.size()) return false; 294 for(int ii = 0; ii < indexedRequest.size(); ii++) 295 { 296 Object actualValue = indexedRequest.get(ii); 297 Object expectedValue = expectedRequest.get(ii); 298 if(!areObjectsEquals(actualValue, expectedValue)) 299 { 300 return false; 301 } 302 } 303 return true; 304 } 305 catch(Exception exc) 306 { 307 throw new NestedApplicationException(exc); 308 } 309 } 310 return false; 311 } 312 313 private boolean areObjectsEquals(Object object1, Object object2) 314 { 315 if(null == object1 && null == object2) return true; 316 if(null == object1) return false; 317 return object1.equals(object2); 318 } 319 320 private boolean isResponseAcceptable(Record response) 321 { 322 return (null == response) || (response instanceof IndexedRecord); 323 } 324 325 private boolean isResponseClassAcceptable(Class responseClass) 326 { 327 return (null == responseClass) || (IndexedRecord.class.isAssignableFrom(responseClass)); 328 } 329 330 /** 331 * First version of the <code>execute</code> methods.<br><br> 332 * This method returns <code>null</code>, if the request does not match 333 * according to the contract of {@link #canHandle}. This never happens under 334 * normal conditions since the {@link InteractionHandler} does not call 335 * <code>execute</code>, if {@link #canHandle} returns <code>false</code>. 336 * <br><br> 337 * Otherwise, this method returns the specified response. If a response 338 * <code>Record</code> object is specified (use {@link #setResponse(Record)}), 339 * it always takes precedence, i.e. the response <code>List</code> will be ignored. 340 * If no <code>Record</code> object is specified, a <code>Record</code> object 341 * is created and filled with the specified response <code>List</code> data. Use the 342 * <code>setResponse</code> methods that take a <code>List</code> 343 * to prepare the response <code>List</code>. The created <code>Record</code> is of the the 344 * specified type (the <code>setResponse</code> method that takes a second 345 * <code>Class</code> parameter allows for specifying a type). If no type 346 * is specified, a {@link com.mockrunner.mock.connector.cci.MockIndexedRecord} 347 * is created. If no response <code>List</code> is specified at all, an empty 348 * {@link com.mockrunner.mock.connector.cci.MockIndexedRecord} 349 * will be returned. 350 * @param interactionSpec the interaction spec 351 * @param actualRequest the actual request 352 * @return the response according to the current request 353 */ 354 public Record execute(InteractionSpec interactionSpec, Record actualRequest) throws ResourceException 355 { 356 if(!canHandle(interactionSpec, actualRequest, null)) return null; 357 if(null != responseRecord) return responseRecord; 358 IndexedRecord response = null; 359 try 360 { 361 if(null == responseClass) 362 { 363 response = new MockIndexedRecord(); 364 } 365 else 366 { 367 response = (IndexedRecord)responseClass.newInstance(); 368 } 369 if(null != responseData) 370 { 371 response.addAll(responseData); 372 } 373 } 374 catch(Exception exc) 375 { 376 ResourceException resExc = new ResourceException("execute() failed"); 377 resExc.setLinkedException(exc); 378 throw resExc; 379 } 380 return (Record)response; 381 } 382 383 /** 384 * Second version of the <code>execute</code> methods.<br><br> 385 * This method returns <code>false</code>, if the request does not match 386 * according to the contract of {@link #canHandle}. This never happens under 387 * normal conditions since the {@link InteractionHandler} does not call 388 * <code>execute</code>, if {@link #canHandle} returns <code>false</code>. 389 * <br><br> 390 * Otherwise, this method fills the response <code>Record</code> with the 391 * specified response <code>List</code> data. Use the <code>setResponse</code> methods that 392 * take a <code>List</code> to prepare the response <code>List</code>. 393 * The response <code>Record</code> must implement <code>IndexedRecord</code> 394 * (it does, otherwise the request would have been rejected by 395 * {@link #canHandle}). If no response <code>List</code> is specified at all, 396 * the response <code>Record</code> is not touched but <code>true</code> 397 * is returned anyway 398 * @param interactionSpec the interaction spec 399 * @param actualRequest the actual request 400 * @param actualResponse the actual response 401 * @return <code>true</code> under normal conditions 402 */ 403 public boolean execute(InteractionSpec interactionSpec, Record actualRequest, Record actualResponse) throws ResourceException 404 { 405 if(!canHandle(interactionSpec, actualRequest, actualResponse)) return false; 406 try 407 { 408 if(null != responseData && null != actualResponse) 409 { 410 ((IndexedRecord)actualResponse).clear(); 411 ((IndexedRecord)actualResponse).addAll(responseData); 412 } 413 } 414 catch(Exception exc) 415 { 416 ResourceException resExc = new ResourceException("execute() failed"); 417 resExc.setLinkedException(exc); 418 throw resExc; 419 } 420 return true; 421 } 422 }