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