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 }