com.eviware.soapui.impl.wsdl.panels.teststeps.amf.SoapUIAMFConnection.java Source code

Java tutorial

Introduction

Here is the source code for com.eviware.soapui.impl.wsdl.panels.teststeps.amf.SoapUIAMFConnection.java

Source

/*
 *  soapUI, copyright (C) 2004-2011 eviware.com 
 *
 *  soapUI is free software; you can redistribute it and/or modify it under the 
 *  terms of version 2.1 of the GNU Lesser General Public License as published by 
 *  the Free Software Foundation.
 *
 *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 *  See the GNU Lesser General Public License for more details at gnu.org.
 */

package com.eviware.soapui.impl.wsdl.panels.teststeps.amf;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpState;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPostMethod;
import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
import com.eviware.soapui.impl.wsdl.support.http.ProxyUtils;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionContext;

import flex.messaging.io.ClassAliasRegistry;
import flex.messaging.io.MessageDeserializer;
import flex.messaging.io.MessageIOConstants;
import flex.messaging.io.SerializationContext;
import flex.messaging.io.amf.ActionContext;
import flex.messaging.io.amf.ActionMessage;
import flex.messaging.io.amf.AmfMessageDeserializer;
import flex.messaging.io.amf.AmfMessageSerializer;
import flex.messaging.io.amf.MessageBody;
import flex.messaging.io.amf.MessageHeader;
import flex.messaging.io.amf.client.AMFHeaderProcessor;
import flex.messaging.io.amf.client.exceptions.ClientStatusException;
import flex.messaging.io.amf.client.exceptions.ServerStatusException;
import flex.messaging.io.amf.client.exceptions.ServerStatusException.HttpResponseInfo;

/**
 * AMFConnection derivate using HttpClient instead of UrlConnection
 * 
 * @author Ole
 */

public class SoapUIAMFConnection {
    private static int DEFAULT_OBJECT_ENCODING = MessageIOConstants.AMF3;

    /**
     * Creates a default AMF connection instance.
     */
    public SoapUIAMFConnection() {
    }

    private ActionContext actionContext;
    private boolean connected;
    private int objectEncoding;
    private boolean objectEncodingSet = false;
    private SerializationContext serializationContext;
    private String url;

    private List<MessageHeader> amfHeaders;
    private AMFHeaderProcessor amfHeaderProcessor;
    private Map<String, String> httpRequestHeaders;
    private int responseCounter;

    private ExtendedPostMethod postMethod;
    private HttpState httpState = new HttpState();
    private PropertyExpansionContext context;

    public int getObjectEncoding() {
        if (!objectEncodingSet)
            return DEFAULT_OBJECT_ENCODING;

        return objectEncoding;
    }

    public void setObjectEncoding(int objectEncoding) {
        this.objectEncoding = objectEncoding;
        objectEncodingSet = true;
    }

    public String getUrl() {
        return url;
    }

    /**
     * Adds an AMF packet-level header which is sent with every request for the
     * life of this AMF connection.
     * 
     * @param name
     *           The name of the header.
     * @param mustUnderstand
     *           Whether the header must be processed or not.
     * @param data
     *           The value of the header.
     */
    public void addAmfHeader(String name, boolean mustUnderstand, Object data) {
        if (amfHeaders == null)
            amfHeaders = new ArrayList<MessageHeader>();

        MessageHeader header = new MessageHeader(name, mustUnderstand, data);
        amfHeaders.add(header);
    }

    /**
     * Add an AMF packet-level header with mustUnderstand=false, which is sent
     * with every request for the life of this AMF connection.
     * 
     * @param name
     *           The name of the header.
     * @param data
     *           The value of the header.
     */
    public void addAmfHeader(String name, Object data) {
        addAmfHeader(name, false, data);
    }

    /**
     * Removes any AMF headers found with the name given.
     * 
     * @param name
     *           The name of the header(s) to remove.
     * 
     * @return true if a header existed with the given name.
     */
    public boolean removeAmfHeader(String name) {
        boolean exists = false;
        if (amfHeaders != null) {
            for (Iterator<MessageHeader> iterator = amfHeaders.iterator(); iterator.hasNext();) {
                MessageHeader header = iterator.next();
                if (name.equals(header.getName())) {
                    iterator.remove();
                    exists = true;
                }
            }
        }
        return exists;
    }

    /**
     * Removes all AMF headers.
     */
    public void removeAllAmfHeaders() {
        if (amfHeaders != null)
            amfHeaders = null;
    }

    /**
     * Adds a Http request header to the underlying connection.
     * 
     * @param name
     *           The name of the Http header.
     * @param value
     *           The value of the Http header.
     */
    public void addHttpRequestHeader(String name, String value) {
        if (httpRequestHeaders == null)
            httpRequestHeaders = new HashMap<String, String>();

        httpRequestHeaders.put(name, value);
    }

    /**
     * Removes the Http header found with the name given.
     * 
     * @param name
     *           The name of the Http header.
     * 
     * @return true if a header existed with the given name.
     */
    public boolean removeHttpRequestHeader(String name) {
        boolean exists = false;
        if (httpRequestHeaders != null) {
            Object previousValue = httpRequestHeaders.remove(name);
            exists = (previousValue != null);
        }
        return exists;
    }

    /**
     * Removes all Http request headers.
     */
    public void removeAllHttpRequestHeaders() {
        if (httpRequestHeaders != null)
            httpRequestHeaders = null;
    }

    /**
     * Makes an AMF request to the server. A connection must have been made prior
     * to making a call.
     * 
     * @param command
     *           The method to call on the server.
     * @param arguments
     *           Arguments for the method.
     * 
     * @return The result of the call.
     * 
     * @throws ClientStatusException
     *            If there is a client side exception.
     * @throws ServerStatusException
     *            If there is a server side exception.
     */

    public Object call(PropertyExpansionContext context, String command, Object... arguments)
            throws ClientStatusException, ServerStatusException {
        this.context = context;

        if (!connected) {
            String message = "AMF connection is not connected";
            ClientStatusException cse = new ClientStatusException(message,
                    ClientStatusException.AMF_CALL_FAILED_CODE);
            throw cse;
        }

        String responseURI = getResponseURI();

        ActionMessage requestMessage = new ActionMessage(getObjectEncoding());

        if (amfHeaders != null) {
            for (MessageHeader header : amfHeaders)
                requestMessage.addHeader(header);
        }

        MessageBody amfMessage = new MessageBody(command, responseURI, arguments);
        requestMessage.addBody(amfMessage);

        // Setup for AMF message serializer
        actionContext.setRequestMessage(requestMessage);
        ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
        AmfMessageSerializer amfMessageSerializer = new AmfMessageSerializer();
        amfMessageSerializer.initialize(serializationContext, outBuffer, null/* debugTrace */);

        try {
            amfMessageSerializer.writeMessage(requestMessage);
            Object result = send(outBuffer);
            return result;
        } catch (Exception e) {
            if (e instanceof ClientStatusException)
                throw (ClientStatusException) e;
            else if (e instanceof ServerStatusException)
                throw (ServerStatusException) e;
            // Otherwise, wrap into a ClientStatusException.
            ClientStatusException exception = new ClientStatusException(e,
                    ClientStatusException.AMF_CALL_FAILED_CODE);
            throw exception;
        } finally {
            try {
                outBuffer.close();
            } catch (IOException ignore) {
            }
        }
    }

    /**
     * Closes the underlying URL connection, sets the url to null, and clears the
     * cookies.
     */
    public void close() {
        // Clear the URL connection and URL.
        if (postMethod != null) {
            postMethod.releaseConnection();
            postMethod = null;
        }
        url = null;

        serializationContext = null;
        connected = false;
    }

    /**
     * Connects to the URL provided. Any previous connections are closed.
     * 
     * @param url
     *           The url to connect to.
     * 
     * @throws ClientStatusException
     *            If there is a client side exception.
     */
    public void connect(String url) throws ClientStatusException {
        if (connected)
            close();

        this.url = url;
        try {
            serializationContext = new SerializationContext();
            serializationContext.createASObjectForMissingType = true;
            internalConnect();
        } catch (IOException e) {
            ClientStatusException exception = new ClientStatusException(e,
                    ClientStatusException.AMF_CONNECT_FAILED_CODE);
            throw exception;
        }
    }

    // --------------------------------------------------------------------------
    //
    // Protected Methods
    //
    // --------------------------------------------------------------------------

    /**
     * Generates the HTTP response info for the server status exception.
     * 
     * @return The HTTP response info for the server status exception.
     */
    protected HttpResponseInfo generateHttpResponseInfo() {
        HttpResponseInfo httpResponseInfo = null;
        try {
            int responseCode = postMethod.getStatusCode();
            String responseMessage = postMethod.getResponseBodyAsString();
            httpResponseInfo = new HttpResponseInfo(responseCode, responseMessage);
        } catch (IOException ignore) {
        }
        return httpResponseInfo;
    }

    /**
     * Generates and returns the response URI.
     * 
     * @return The response URI.
     */
    protected String getResponseURI() {
        String responseURI = "/" + responseCounter;
        responseCounter++;
        return responseURI;
    }

    /**
     * An internal method that sets up the underlying URL connection.
     * 
     * @throws IOException
     *            If an exception is encountered during URL connection setup.
     */
    protected void internalConnect() throws IOException {
        serializationContext.instantiateTypes = false;
        postMethod = new ExtendedPostMethod(url);
        setHttpRequestHeaders();
        actionContext = new ActionContext();
        connected = true;
    }

    /**
     * Processes the HTTP response headers and body.
     */
    protected Object processHttpResponse(InputStream inputStream)
            throws ClassNotFoundException, IOException, ClientStatusException, ServerStatusException {
        return processHttpResponseBody(inputStream);
    }

    /**
     * Processes the HTTP response body.
     */
    protected Object processHttpResponseBody(InputStream inputStream)
            throws ClassNotFoundException, IOException, ClientStatusException, ServerStatusException {
        DataInputStream din = new DataInputStream(inputStream);
        ActionMessage message = new ActionMessage();
        actionContext.setRequestMessage(message);
        MessageDeserializer deserializer = new AmfMessageDeserializer();
        deserializer.initialize(serializationContext, din, null/* trace */);
        deserializer.readMessage(message, actionContext);
        din.close();
        context.setProperty(AMFResponse.AMF_RESPONSE_ACTION_MESSAGE, message);
        return processAmfPacket(message);
    }

    /**
     * Processes the AMF packet.
     */
    @SuppressWarnings("unchecked")
    protected Object processAmfPacket(ActionMessage packet) throws ClientStatusException, ServerStatusException {
        processAmfHeaders(packet.getHeaders());
        return processAmfBody(packet.getBodies());
    }

    /**
     * Processes the AMF headers by dispatching them to an AMF header processor,
     * if one exists.
     */
    protected void processAmfHeaders(ArrayList<MessageHeader> headers) throws ClientStatusException {
        // No need to process headers if there's no AMF header processor.
        if (amfHeaderProcessor == null)
            return;

        for (MessageHeader header : headers)
            amfHeaderProcessor.processHeader(header);
    }

    /**
     * Processes the AMF body. Note that this method won't work if batching of
     * AMF messages is supported at some point but for now we are guaranteed to
     * have a single message.
     */
    protected Object processAmfBody(ArrayList<MessageBody> messages) throws ServerStatusException {
        for (MessageBody message : messages) {
            String targetURI = message.getTargetURI();

            if (targetURI.endsWith(MessageIOConstants.RESULT_METHOD)) {
                return message.getData();
            } else if (targetURI.endsWith(MessageIOConstants.STATUS_METHOD)) {
                // String exMessage = "Server error";
                // HttpResponseInfo responseInfo = generateHttpResponseInfo();
                // ServerStatusException exception = new ServerStatusException(
                // exMessage, message.getData(), responseInfo );

                return message.getData();
                // throw exception;
            }
        }
        return null; // Should not happen.
    }

    /**
     * Writes the output buffer and processes the HTTP response.
     */
    protected Object send(ByteArrayOutputStream outBuffer)
            throws ClassNotFoundException, IOException, ClientStatusException, ServerStatusException {
        // internalConnect.
        internalConnect();

        postMethod.setRequestEntity(new ByteArrayRequestEntity(outBuffer.toByteArray()));
        HostConfiguration hostConfiguration = new HostConfiguration();

        ProxyUtils.initProxySettings(
                context.getModelItem() == null ? SoapUI.getSettings() : context.getModelItem().getSettings(),
                httpState, hostConfiguration, url, context);

        HttpClientSupport.getHttpClient().executeMethod(hostConfiguration, postMethod, httpState);

        context.setProperty(AMFResponse.AMF_POST_METHOD, postMethod);

        return processHttpResponse(responseBodyInputStream());
    }

    private ByteArrayInputStream responseBodyInputStream() throws IOException {
        byte[] responseBody = postMethod.getResponseBody();
        ByteArrayInputStream bais = new ByteArrayInputStream(responseBody);
        context.setProperty(AMFResponse.AMF_RAW_RESPONSE_BODY, responseBody);
        return bais;
    }

    /**
     * Sets the Http request headers, including the cookie headers.
     */
    protected void setHttpRequestHeaders() {
        if (httpRequestHeaders != null) {
            for (Map.Entry<String, String> element : httpRequestHeaders.entrySet()) {
                String key = element.getKey();
                String value = element.getValue();
                postMethod.setRequestHeader(key, value);
            }
        }
    }

    /**
     * Registers a custom alias for a class name bidirectionally.
     * 
     * @param alias
     *           The alias for the class name.
     * @param className
     *           The concrete class name.
     */
    public static void registerAlias(String alias, String className) {
        ClassAliasRegistry registry = ClassAliasRegistry.getRegistry();
        registry.registerAlias(alias, className);
        registry.registerAlias(className, alias);
    }
}