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