001: package com.mockrunner.connector;
002:
003: import java.util.HashMap;
004: import java.util.Iterator;
005: import java.util.Map;
006:
007: import javax.resource.ResourceException;
008: import javax.resource.cci.InteractionSpec;
009: import javax.resource.cci.MappedRecord;
010: import javax.resource.cci.Record;
011:
012: import com.mockrunner.base.NestedApplicationException;
013: import com.mockrunner.mock.connector.cci.MockMappedRecord;
014:
015: /**
016: * This interaction implementor works with mapped records. It takes a <code>Map</code> for
017: * the request and a <code>Map</code> or a <code>Record</code> instance for the response.
018: * If the request <code>Map</code> is <code>null</code>, which is the default,
019: * the implementor accepts any request and returns the specified result. If a request
020: * <code>Map</code> is specified, this implementor accepts only requests that contain the same
021: * data as the specified expected request <code>Map</code>. The underlying maps are compared
022: * as described in the <code>Map.equals</code> method.
023: * If a request is accepted, this implementor replies with the specified
024: * response. You can use the various constructors and <code>set</code> methods
025: * to configure the expected request and the response.<br>
026: * Please check out the documentation of the various methods for details.
027: */
028: public class MappedRecordInteraction implements InteractionImplementor {
029: private boolean enabled;
030: private Map expectedRequest;
031: private Map 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 MappedRecordInteraction() {
040: this (null, null, MockMappedRecord.class);
041: }
042:
043: /**
044: * Sets the expected request to <code>null</code> and prepares
045: * the specified response <code>Map</code>. The response class for the
046: * {@link #execute(InteractionSpec,Record)} method is set
047: * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}.
048: * It is allowed to pass <code>null</code> for the response <code>Map</code>
049: * which is equivalent to an empty response.
050: * The specified response is returned for every request.
051: * @param responseMap the response <code>Map</code>
052: */
053: public MappedRecordInteraction(Map responseMap) {
054: this (null, responseMap, MockMappedRecord.class);
055: }
056:
057: /**
058: * Sets the specified expected request <code>Map</code> and prepares
059: * the specified response <code>Map</code>. The response class for the
060: * {@link #execute(InteractionSpec,Record)} method is set
061: * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}.
062: * It is allowed to pass <code>null</code> for the request and response <code>Map</code>
063: * which is equivalent to an empty expected request (i.e. every request is accepted)
064: * or to an empty response respectively.
065: * The specified response is returned, if the actual request matches the specified expected
066: * request.
067: * @param expectedRequest the expected request <code>Map</code>
068: * @param responseMap the response <code>Map</code>
069: */
070: public MappedRecordInteraction(Map expectedRequest, Map responseMap) {
071: this (expectedRequest, responseMap, MockMappedRecord.class);
072: }
073:
074: /**
075: * Sets the expected request to <code>null</code> and prepares
076: * the specified response <code>Map</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>MappedRecord</code>,
080: * otherwise an <code>IllegalArgumentException</code> will be thrown.
081: * It is allowed to pass <code>null</code> for the response <code>Map</code>
082: * which is equivalent to an empty response.
083: * The specified response is returned for every request.
084: * @param responseMap the response <code>Map</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 MappedRecordInteraction(Map responseMap, Class responseClass) {
090: this (null, responseMap, responseClass);
091: }
092:
093: /**
094: * Sets the specified expected request <code>Map</code> and prepares
095: * the specified response <code>Map</code>. The response class for the
096: * {@link #execute(InteractionSpec,Record)} method is set
097: * to the specified <code>responseClass</code>. The specified
098: * <code>responseClass</code> must implement <code>MappedRecord</code>,
099: * otherwise an <code>IllegalArgumentException</code> will be thrown.
100: * It is allowed to pass <code>null</code> for the request and response <code>Map</code>
101: * which is equivalent to an empty expected request (i.e. every request is accepted)
102: * or to an empty response respectively.
103: * The specified response is returned, if the actual request matches the specified expected
104: * request.
105: * @param expectedRequest the expected request <code>Map</code>
106: * @param responseMap the response <code>Map</code
107: * @param responseClass the response <code>Record</code> class
108: * @throws IllegalArgumentException if the <code>responseClass</code>
109: * is not valid
110: */
111: public MappedRecordInteraction(Map expectedRequest,
112: Map responseMap, Class responseClass) {
113: setExpectedRequest(expectedRequest);
114: setResponse(responseMap, responseClass);
115: this .enabled = true;
116: }
117:
118: /**
119: * Sets the specified expected request <code>Map</code> and the response
120: * <code>Record</code> for the {@link #execute(InteractionSpec, Record)}
121: * method. The response <code>Record</code> is ignored for
122: * {@link #execute(InteractionSpec,Record,Record)} but takes precedence
123: * over the specified response <code>Map</code> for {@link #execute(InteractionSpec, Record)}.
124: * It is allowed to pass <code>null</code> for the request and response <code>Record</code>
125: * which is equivalent to an empty expected request (i.e. every request is accepted)
126: * or to no specified response <code>Record</code>, i.e. the specified response
127: * <code>Map</code> is taken.
128: * The specified response is returned, if the actual request matches the specified expected
129: * request.
130: * @param expectedRequest the expected request <code>Map</code>
131: * @param responseRecord the response <code>Record</code>
132: */
133: public MappedRecordInteraction(Map expectedRequest,
134: Record responseRecord) {
135: setExpectedRequest(expectedRequest);
136: setResponse(responseRecord);
137: this .enabled = true;
138: }
139:
140: /**
141: * Sets the expected request to <code>null</code> and prepares the response
142: * <code>Record</code> for the {@link #execute(InteractionSpec, Record)}
143: * method. The response <code>Record</code> is ignored for
144: * {@link #execute(InteractionSpec,Record,Record)} but takes precedence
145: * over the specified response <code>Map</code> for {@link #execute(InteractionSpec, Record)}.
146: * It is allowed to pass <code>null</code> for the response <code>Record</code>
147: * which is equivalent to no specified response <code>Record</code>, i.e. the specified response
148: * <code>Map</code> is taken.
149: * The specified response is returned for every request.
150: * @param responseRecord the response <code>Record</code>
151: */
152: public MappedRecordInteraction(Record responseRecord) {
153: this (null, responseRecord);
154: }
155:
156: /**
157: * Enables this implementor.
158: */
159: public void enable() {
160: this .enabled = true;
161: }
162:
163: /**
164: * Disables this implementor. {@link #canHandle(InteractionSpec, Record, Record)}
165: * always returns <code>false</code>, if this implementor is disabled.
166: */
167: public void disable() {
168: this .enabled = false;
169: }
170:
171: /**
172: * Sets the specified expected request <code>Map</code>. The response is returned,
173: * if the actual request matches the specified expected request <code>Map</code>
174: * according to <code>Map.equals</code>.
175: * It is allowed to pass <code>null</code> for the request <code>Map</code>
176: * which is equivalent to an empty expected request (i.e. every request
177: * is accepted).
178: * @param expectedRequest the expected request <code>Map</code>
179: */
180: public void setExpectedRequest(Map expectedRequest) {
181: if (null == expectedRequest) {
182: this .expectedRequest = null;
183: } else {
184: this .expectedRequest = new HashMap(expectedRequest);
185: }
186: }
187:
188: /**
189: * Prepares the specified response <code>Map</code>. The response class for the
190: * {@link #execute(InteractionSpec,Record)} method is set
191: * to the default {@link com.mockrunner.mock.connector.cci.MockMappedRecord}.
192: * It is allowed to pass <code>null</code> for the response <code>Map</code>
193: * which is equivalent to an empty response.
194: * @param responseMap the response <code>Map</code
195: */
196: public void setResponse(Map responseMap) {
197: setResponse(responseMap, MockMappedRecord.class);
198: }
199:
200: /**
201: * Prepares the specified response <code>Map</code>. The response class for the
202: * {@link #execute(InteractionSpec,Record)} method is set
203: * to the specified <code>responseClass</code>. The specified
204: * <code>responseClass</code> must implement <code>MappedRecord</code>,
205: * otherwise an <code>IllegalArgumentException</code> will be thrown.
206: * It is allowed to pass <code>null</code> for the response <code>Map</code>
207: * which is equivalent to an empty response.
208: * @param responseMap the response <code>Map</code>
209: * @param responseClass the response <code>Record</code> class
210: * @throws IllegalArgumentException if the <code>responseClass</code>
211: * is not valid
212: */
213: public void setResponse(Map responseMap, Class responseClass) {
214: if (!isResponseClassAcceptable(responseClass)) {
215: throw new IllegalArgumentException(
216: "responseClass must implement "
217: + MappedRecord.class.getName());
218: }
219: if (null == responseMap) {
220: this .responseData = null;
221: } else {
222: this .responseData = new HashMap(responseMap);
223: }
224: this .responseClass = responseClass;
225: }
226:
227: /**
228: * Prepares the response <code>Record</code> for the
229: * {@link #execute(InteractionSpec, Record)} method. The response
230: * <code>Record</code> is ignored for {@link #execute(InteractionSpec,Record,Record)}
231: * but takes precedence over the specified response <code>Map</code> for
232: * {@link #execute(InteractionSpec, Record)}.
233: * It is allowed to pass <code>null</code> for the response <code>Record</code>
234: * which is equivalent to no specified response <code>Record</code>, i.e. the specified response
235: * <code>Map</code> is taken.
236: * @param responseRecord the response <code>Record</code>
237: */
238: public void setResponse(Record responseRecord) {
239: this .responseRecord = responseRecord;
240: }
241:
242: /**
243: * Returns <code>true</code> if this implementor is enabled and will handle the request.
244: * This method returns <code>true</code> if the following prerequisites are fulfilled:<br><br>
245: * It is enabled.<br><br>
246: * The response <code>Record</code> must implement <code>MappedRecord</code>
247: * or it must be <code>null</code> (which is the case, if the actual request
248: * targets the {@link #execute(InteractionSpec,Record)} method instead of
249: * {@link #execute(InteractionSpec,Record,Record)}).<br><br>
250: * The expected request must be <code>null</code> (use the various
251: * <code>setExpectedRequest</code> methods) or the actual request <code>Record</code>
252: * must implement <code>MappedRecord</code> and must contain the same data as
253: * the specified expected request <code>Map</code> according to <code>Map.equals</code>.<br><br>
254: * Otherwise, <code>false</code> is returned.
255: * @param interactionSpec the <code>InteractionSpec</code> for the actual call
256: * @param actualRequest the request for the actual call
257: * @param actualResponse the response for the actual call, may be <code>null</code>
258: * @return <code>true</code> if this implementor will handle the request and
259: * will return the specified response, <code>false</code> otherwise
260: */
261: public boolean canHandle(InteractionSpec interactionSpec,
262: Record actualRequest, Record actualResponse) {
263: if (!enabled)
264: return false;
265: if (!isResponseAcceptable(actualResponse))
266: return false;
267: return doesRequestMatch(actualRequest);
268: }
269:
270: private boolean doesRequestMatch(Record request) {
271: if (null == expectedRequest)
272: return true;
273: if (null == request)
274: return false;
275: if (request instanceof MappedRecord) {
276: try {
277: MappedRecord mappedRequest = (MappedRecord) request;
278: if (mappedRequest.size() != expectedRequest.size())
279: return false;
280: Iterator keys = mappedRequest.keySet().iterator();
281: while (keys.hasNext()) {
282: Object nextKey = keys.next();
283: Object actualValue = mappedRequest.get(nextKey);
284: Object expectedValue = expectedRequest.get(nextKey);
285: if (!areObjectsEquals(actualValue, expectedValue)) {
286: return false;
287: }
288: }
289: return true;
290: } catch (Exception exc) {
291: throw new NestedApplicationException(exc);
292: }
293: }
294: return false;
295: }
296:
297: private boolean areObjectsEquals(Object object1, Object object2) {
298: if (null == object1 && null == object2)
299: return true;
300: if (null == object1)
301: return false;
302: return object1.equals(object2);
303: }
304:
305: private boolean isResponseAcceptable(Record response) {
306: return (null == response) || (response instanceof MappedRecord);
307: }
308:
309: private boolean isResponseClassAcceptable(Class responseClass) {
310: return (null == responseClass)
311: || (MappedRecord.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>Map</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>Map</code> data. Use the
326: * <code>setResponse</code> methods that take a <code>Map</code>
327: * to prepare the response <code>Map</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.MockMappedRecord}
331: * is created. If no response <code>Map</code> is specified at all, an empty
332: * {@link com.mockrunner.mock.connector.cci.MockMappedRecord}
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: MappedRecord response = null;
345: try {
346: if (null == responseClass) {
347: response = new MockMappedRecord();
348: } else {
349: response = (MappedRecord) responseClass.newInstance();
350: }
351: if (null != responseData) {
352: response.putAll(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>Map</code> data. Use the <code>setResponse</code> methods that
372: * take a <code>Map</code> to prepare the response <code>Map</code>.
373: * The response <code>Record</code> must implement <code>MappedRecord</code>
374: * (it does, otherwise the request would have been rejected by
375: * {@link #canHandle}). If no response <code>Map</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: ((MappedRecord) actualResponse).clear();
391: ((MappedRecord) actualResponse).putAll(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: }
|