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