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