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    }