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