1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wsdl.panels.teststeps.amf;
14
15 import java.io.ByteArrayInputStream;
16 import java.io.ByteArrayOutputStream;
17 import java.io.DataInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25
26 import org.apache.commons.httpclient.HostConfiguration;
27 import org.apache.commons.httpclient.HttpState;
28 import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
29
30 import com.eviware.soapui.SoapUI;
31 import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPostMethod;
32 import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
33 import com.eviware.soapui.impl.wsdl.support.http.ProxyUtils;
34 import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContext;
35
36 import flex.messaging.io.ClassAliasRegistry;
37 import flex.messaging.io.MessageDeserializer;
38 import flex.messaging.io.MessageIOConstants;
39 import flex.messaging.io.SerializationContext;
40 import flex.messaging.io.amf.ActionContext;
41 import flex.messaging.io.amf.ActionMessage;
42 import flex.messaging.io.amf.AmfMessageDeserializer;
43 import flex.messaging.io.amf.AmfMessageSerializer;
44 import flex.messaging.io.amf.MessageBody;
45 import flex.messaging.io.amf.MessageHeader;
46 import flex.messaging.io.amf.client.AMFHeaderProcessor;
47 import flex.messaging.io.amf.client.exceptions.ClientStatusException;
48 import flex.messaging.io.amf.client.exceptions.ServerStatusException;
49 import flex.messaging.io.amf.client.exceptions.ServerStatusException.HttpResponseInfo;
50
51 /***
52 * AMFConnection derivate using HttpClient instead of UrlConnection
53 *
54 * @author Ole
55 */
56
57 public class SoapUIAMFConnection
58 {
59 private static int DEFAULT_OBJECT_ENCODING = MessageIOConstants.AMF3;
60
61 /***
62 * Creates a default AMF connection instance.
63 */
64 public SoapUIAMFConnection()
65 {
66 }
67
68 private ActionContext actionContext;
69 private boolean connected;
70 private int objectEncoding;
71 private boolean objectEncodingSet = false;
72 private SerializationContext serializationContext;
73 private String url;
74
75 private List<MessageHeader> amfHeaders;
76 private AMFHeaderProcessor amfHeaderProcessor;
77 private Map<String, String> httpRequestHeaders;
78 private int responseCounter;
79
80 private ExtendedPostMethod postMethod;
81 private HttpState httpState = new HttpState();
82 private PropertyExpansionContext context;
83
84 public int getObjectEncoding()
85 {
86 if( !objectEncodingSet )
87 return DEFAULT_OBJECT_ENCODING;
88
89 return objectEncoding;
90 }
91
92 public void setObjectEncoding( int objectEncoding )
93 {
94 this.objectEncoding = objectEncoding;
95 objectEncodingSet = true;
96 }
97
98 public String getUrl()
99 {
100 return url;
101 }
102
103 /***
104 * Adds an AMF packet-level header which is sent with every request for the
105 * life of this AMF connection.
106 *
107 * @param name
108 * The name of the header.
109 * @param mustUnderstand
110 * Whether the header must be processed or not.
111 * @param data
112 * The value of the header.
113 */
114 public void addAmfHeader( String name, boolean mustUnderstand, Object data )
115 {
116 if( amfHeaders == null )
117 amfHeaders = new ArrayList<MessageHeader>();
118
119 MessageHeader header = new MessageHeader( name, mustUnderstand, data );
120 amfHeaders.add( header );
121 }
122
123 /***
124 * Add an AMF packet-level header with mustUnderstand=false, which is sent
125 * with every request for the life of this AMF connection.
126 *
127 * @param name
128 * The name of the header.
129 * @param data
130 * The value of the header.
131 */
132 public void addAmfHeader( String name, Object data )
133 {
134 addAmfHeader( name, false, data );
135 }
136
137 /***
138 * Removes any AMF headers found with the name given.
139 *
140 * @param name
141 * The name of the header(s) to remove.
142 *
143 * @return true if a header existed with the given name.
144 */
145 public boolean removeAmfHeader( String name )
146 {
147 boolean exists = false;
148 if( amfHeaders != null )
149 {
150 for( Iterator<MessageHeader> iterator = amfHeaders.iterator(); iterator.hasNext(); )
151 {
152 MessageHeader header = iterator.next();
153 if( name.equals( header.getName() ) )
154 {
155 iterator.remove();
156 exists = true;
157 }
158 }
159 }
160 return exists;
161 }
162
163 /***
164 * Removes all AMF headers.
165 */
166 public void removeAllAmfHeaders()
167 {
168 if( amfHeaders != null )
169 amfHeaders = null;
170 }
171
172 /***
173 * Adds a Http request header to the underlying connection.
174 *
175 * @param name
176 * The name of the Http header.
177 * @param value
178 * The value of the Http header.
179 */
180 public void addHttpRequestHeader( String name, String value )
181 {
182 if( httpRequestHeaders == null )
183 httpRequestHeaders = new HashMap<String, String>();
184
185 httpRequestHeaders.put( name, value );
186 }
187
188 /***
189 * Removes the Http header found with the name given.
190 *
191 * @param name
192 * The name of the Http header.
193 *
194 * @return true if a header existed with the given name.
195 */
196 public boolean removeHttpRequestHeader( String name )
197 {
198 boolean exists = false;
199 if( httpRequestHeaders != null )
200 {
201 Object previousValue = httpRequestHeaders.remove( name );
202 exists = ( previousValue != null );
203 }
204 return exists;
205 }
206
207 /***
208 * Removes all Http request headers.
209 */
210 public void removeAllHttpRequestHeaders()
211 {
212 if( httpRequestHeaders != null )
213 httpRequestHeaders = null;
214 }
215
216 /***
217 * Makes an AMF request to the server. A connection must have been made prior
218 * to making a call.
219 *
220 * @param command
221 * The method to call on the server.
222 * @param arguments
223 * Arguments for the method.
224 *
225 * @return The result of the call.
226 *
227 * @throws ClientStatusException
228 * If there is a client side exception.
229 * @throws ServerStatusException
230 * If there is a server side exception.
231 */
232
233 public Object call( PropertyExpansionContext context, String command, Object... arguments )
234 throws ClientStatusException, ServerStatusException
235 {
236 this.context = context;
237
238 if( !connected )
239 {
240 String message = "AMF connection is not connected";
241 ClientStatusException cse = new ClientStatusException( message, ClientStatusException.AMF_CALL_FAILED_CODE );
242 throw cse;
243 }
244
245 String responseURI = getResponseURI();
246
247 ActionMessage requestMessage = new ActionMessage( getObjectEncoding() );
248
249 if( amfHeaders != null )
250 {
251 for( MessageHeader header : amfHeaders )
252 requestMessage.addHeader( header );
253 }
254
255 MessageBody amfMessage = new MessageBody( command, responseURI, arguments );
256 requestMessage.addBody( amfMessage );
257
258
259 actionContext.setRequestMessage( requestMessage );
260 ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
261 AmfMessageSerializer amfMessageSerializer = new AmfMessageSerializer();
262 amfMessageSerializer.initialize( serializationContext, outBuffer, null
263
264 try
265 {
266 amfMessageSerializer.writeMessage( requestMessage );
267 Object result = send( outBuffer );
268 return result;
269 }
270 catch( Exception e )
271 {
272 if( e instanceof ClientStatusException )
273 throw ( ClientStatusException )e;
274 else if( e instanceof ServerStatusException )
275 throw ( ServerStatusException )e;
276
277 ClientStatusException exception = new ClientStatusException( e, ClientStatusException.AMF_CALL_FAILED_CODE );
278 throw exception;
279 }
280 finally
281 {
282 try
283 {
284 outBuffer.close();
285 }
286 catch( IOException ignore )
287 {
288 }
289 }
290 }
291
292 /***
293 * Closes the underlying URL connection, sets the url to null, and clears the
294 * cookies.
295 */
296 public void close()
297 {
298
299 if( postMethod != null )
300 {
301 postMethod.releaseConnection();
302 postMethod = null;
303 }
304 url = null;
305
306 serializationContext = null;
307 connected = false;
308 }
309
310 /***
311 * Connects to the URL provided. Any previous connections are closed.
312 *
313 * @param url
314 * The url to connect to.
315 *
316 * @throws ClientStatusException
317 * If there is a client side exception.
318 */
319 public void connect( String url ) throws ClientStatusException
320 {
321 if( connected )
322 close();
323
324 this.url = url;
325 try
326 {
327 serializationContext = new SerializationContext();
328 serializationContext.createASObjectForMissingType = true;
329 internalConnect();
330 }
331 catch( IOException e )
332 {
333 ClientStatusException exception = new ClientStatusException( e, ClientStatusException.AMF_CONNECT_FAILED_CODE );
334 throw exception;
335 }
336 }
337
338
339
340
341
342
343
344 /***
345 * Generates the HTTP response info for the server status exception.
346 *
347 * @return The HTTP response info for the server status exception.
348 */
349 protected HttpResponseInfo generateHttpResponseInfo()
350 {
351 HttpResponseInfo httpResponseInfo = null;
352 try
353 {
354 int responseCode = postMethod.getStatusCode();
355 String responseMessage = postMethod.getResponseBodyAsString();
356 httpResponseInfo = new HttpResponseInfo( responseCode, responseMessage );
357 }
358 catch( IOException ignore )
359 {
360 }
361 return httpResponseInfo;
362 }
363
364 /***
365 * Generates and returns the response URI.
366 *
367 * @return The response URI.
368 */
369 protected String getResponseURI()
370 {
371 String responseURI = "/" + responseCounter;
372 responseCounter++ ;
373 return responseURI;
374 }
375
376 /***
377 * An internal method that sets up the underlying URL connection.
378 *
379 * @throws IOException
380 * If an exception is encountered during URL connection setup.
381 */
382 protected void internalConnect() throws IOException
383 {
384 serializationContext.instantiateTypes = false;
385 postMethod = new ExtendedPostMethod( url );
386 setHttpRequestHeaders();
387 actionContext = new ActionContext();
388 connected = true;
389 }
390
391 /***
392 * Processes the HTTP response headers and body.
393 */
394 protected Object processHttpResponse( InputStream inputStream ) throws ClassNotFoundException, IOException,
395 ClientStatusException, ServerStatusException
396 {
397 return processHttpResponseBody( inputStream );
398 }
399
400 /***
401 * Processes the HTTP response body.
402 */
403 protected Object processHttpResponseBody( InputStream inputStream ) throws ClassNotFoundException, IOException,
404 ClientStatusException, ServerStatusException
405 {
406 DataInputStream din = new DataInputStream( inputStream );
407 ActionMessage message = new ActionMessage();
408 actionContext.setRequestMessage( message );
409 MessageDeserializer deserializer = new AmfMessageDeserializer();
410 deserializer.initialize( serializationContext, din, null
411 deserializer.readMessage( message, actionContext );
412 din.close();
413 context.setProperty( AMFResponse.AMF_RESPONSE_ACTION_MESSAGE, message );
414 return processAmfPacket( message );
415 }
416
417 /***
418 * Processes the AMF packet.
419 */
420 @SuppressWarnings( "unchecked" )
421 protected Object processAmfPacket( ActionMessage packet ) throws ClientStatusException, ServerStatusException
422 {
423 processAmfHeaders( packet.getHeaders() );
424 return processAmfBody( packet.getBodies() );
425 }
426
427 /***
428 * Processes the AMF headers by dispatching them to an AMF header processor,
429 * if one exists.
430 */
431 protected void processAmfHeaders( ArrayList<MessageHeader> headers ) throws ClientStatusException
432 {
433
434 if( amfHeaderProcessor == null )
435 return;
436
437 for( MessageHeader header : headers )
438 amfHeaderProcessor.processHeader( header );
439 }
440
441 /***
442 * Processes the AMF body. Note that this method won't work if batching of
443 * AMF messages is supported at some point but for now we are guaranteed to
444 * have a single message.
445 */
446 protected Object processAmfBody( ArrayList<MessageBody> messages ) throws ServerStatusException
447 {
448 for( MessageBody message : messages )
449 {
450 String targetURI = message.getTargetURI();
451
452 if( targetURI.endsWith( MessageIOConstants.RESULT_METHOD ) )
453 {
454 return message.getData();
455 }
456 else if( targetURI.endsWith( MessageIOConstants.STATUS_METHOD ) )
457 {
458
459
460
461
462
463 return message.getData();
464
465 }
466 }
467 return null;
468 }
469
470 /***
471 * Writes the output buffer and processes the HTTP response.
472 */
473 protected Object send( ByteArrayOutputStream outBuffer ) throws ClassNotFoundException, IOException,
474 ClientStatusException, ServerStatusException
475 {
476
477 internalConnect();
478
479 postMethod.setRequestEntity( new ByteArrayRequestEntity( outBuffer.toByteArray() ) );
480 HostConfiguration hostConfiguration = new HostConfiguration();
481
482 ProxyUtils.initProxySettings( context.getModelItem() == null ? SoapUI.getSettings() : context.getModelItem()
483 .getSettings(), httpState, hostConfiguration, url, context );
484
485 HttpClientSupport.getHttpClient().executeMethod( hostConfiguration, postMethod, httpState );
486
487 context.setProperty( AMFResponse.AMF_POST_METHOD, postMethod );
488
489 return processHttpResponse( responseBodyInputStream() );
490 }
491
492 private ByteArrayInputStream responseBodyInputStream() throws IOException
493 {
494 byte[] responseBody = postMethod.getResponseBody();
495 ByteArrayInputStream bais = new ByteArrayInputStream( responseBody );
496 context.setProperty( AMFResponse.AMF_RAW_RESPONSE_BODY, responseBody );
497 return bais;
498 }
499
500 /***
501 * Sets the Http request headers, including the cookie headers.
502 */
503 protected void setHttpRequestHeaders()
504 {
505 if( httpRequestHeaders != null )
506 {
507 for( Map.Entry<String, String> element : httpRequestHeaders.entrySet() )
508 {
509 String key = element.getKey();
510 String value = element.getValue();
511 postMethod.setRequestHeader( key, value );
512 }
513 }
514 }
515
516 /***
517 * Registers a custom alias for a class name bidirectionally.
518 *
519 * @param alias
520 * The alias for the class name.
521 * @param className
522 * The concrete class name.
523 */
524 public static void registerAlias( String alias, String className )
525 {
526 ClassAliasRegistry registry = ClassAliasRegistry.getRegistry();
527 registry.registerAlias( alias, className );
528 registry.registerAlias( className, alias );
529 }
530 }