Package net.sourceforge.stripes.mock

Source Code of net.sourceforge.stripes.mock.MockRoundtrip

/* Copyright 2005-2006 Tim Fennell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sourceforge.stripes.mock;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.Filter;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.controller.ActionResolver;
import net.sourceforge.stripes.controller.AnnotatedClassActionResolver;
import net.sourceforge.stripes.controller.StripesConstants;
import net.sourceforge.stripes.controller.StripesFilter;
import net.sourceforge.stripes.controller.UrlBindingFactory;
import net.sourceforge.stripes.util.CryptoUtil;
import net.sourceforge.stripes.validation.ValidationErrors;

/**
* <p>Mock object that attempts to make it easier to use the other Mock objects in this package
* to interact with Stripes and to interrogate the results.  Everything that is done in this class
* is do-able without this class! It simply exists to make things a bit easier. As a result all
* the methods in this class simply manipulate one or more of the underlying Mock objects. If
* some needed capability is not exposed through the MockRoundtrip it is always possible to fetch
* the underlying request, response and context and interact with them directly.</p>
*
* <p>It is worth noting that the Mock system <b>does not process forwards, includes and
*  redirects</b>. When an ActionBean (or other object) invokes the servlet APIs for any of these
* actions it is recorded so that it can be reported and verified later. In the majority of cases
* it should be sufficient to test ActionBeans in isolation and verify that they produced the
* expected output data and/or forward/redirect. If your ActionBeans depend on being able to include
* other resources before continuing, sorry - you're on your own!</p>
*
* <p>An example usage of this class might look like:</p>
*
* <pre>
* MockServletContext context = ...;
* MockRoundtrip trip = new MockRoundtrip(context, CalculatorActionBean.class);
* trip.setParameter("numberOne", "2");
* trip.setParameter("numberTwo", "2");
* trip.execute();
* CalculatorActionBean bean = trip.getActionBean(CalculatorActionBean.class);
* Assert.assertEquals(bean.getResult(), 4, "two plus two should equal four");
* Assert.assertEquals(trip.getDestination(), ""/quickstart/index.jsp");
* </pre>
*
* @author Tim Fennell
* @since Stripes 1.1.1
*/
public class MockRoundtrip {
    /** Default value for the source page that generated this round trip request. */
    public static final String DEFAULT_SOURCE_PAGE = "_default_source_page_";

    private MockHttpServletRequest request;
    private MockHttpServletResponse response;
    private MockServletContext context;

    /**
     * Preferred constructor that will manufacture a request. Uses the ServletContext to ensure
     * that the request's context path matches. Pulls the UrlBinding of the ActionBean and uses
     * that as the requst URL. Constructs a new session for the request.
     *
     * @param context the MockServletContext that will receive this request
     * @param beanType a Class object representing the ActionBean that should receive the request
     */
    public MockRoundtrip(MockServletContext context, Class<? extends ActionBean> beanType) {
        this(context, beanType, new MockHttpSession(context) );
    }

    /**
     * Preferred constructor that will manufacture a request. Uses the ServletContext to ensure
     * that the request's context path matches. Pulls the UrlBinding of the ActionBean and uses
     * that as the requst URL. Constructs a new session for the request.
     *
     * @param context the MockServletContext that will receive this request
     * @param beanType a Class object representing the ActionBean that should receive the request
     */
    public MockRoundtrip(MockServletContext context,
                         Class<? extends ActionBean> beanType,
                         MockHttpSession session) {
        this(context, getUrlBindingStub(beanType, context), session);
    }

    /**
     * Constructor that will create a requeset suitable for the provided servlet context and
     * URL. Note that in general the contructors that take an ActionBean Class object are preferred
     * over those that take a URL.  Constructs a new session for the request.
     *
     * @param context the MockServletContext that will receive this request
     * @param actionBeanUrl the url binding of the action bean
     */
    public MockRoundtrip(MockServletContext context, String actionBeanUrl) {
        this(context, actionBeanUrl, new MockHttpSession(context));
    }

    /**
     * Constructor that will create a requeset suitable for the provided servlet context and
     * URL. Note that in general the contructors that take an ActionBean Class object are preferred
     * over those that take a URL.  The request will use the provided session instead of creating
     * a new one.
     *
     * @param context the MockServletContext that will receive this request
     * @param actionBeanUrl the url binding of the action bean
     * @param session an instance of MockHttpSession to use for the request
     */
    public MockRoundtrip(MockServletContext context, String actionBeanUrl, MockHttpSession session) {
        // Look for a query string and parse out the parameters if one is present
        String path = actionBeanUrl;
        SortedMap<String, List<String>> parameters = null;
        int qmark = actionBeanUrl.indexOf("?");
        if (qmark > 0) {
            path = actionBeanUrl.substring(0, qmark);
            if (qmark < actionBeanUrl.length()) {
                String query = actionBeanUrl.substring(qmark + 1);
                if (query != null && query.length() > 0) {
                    parameters = new TreeMap<String, List<String>>();
                    for (String kv : query.split("&")) {
                        String[] parts = kv.split("=");
                        String key, value;
                        if (parts.length == 1) {
                            key = parts[0];
                            value = null;
                        }
                        else if (parts.length == 2) {
                            key = parts[0];
                            value = parts[1];
                        }
                        else {
                            key = value = null;
                        }

                        if (key != null) {
                            List<String> values = parameters.get(key);
                            if (values == null)
                                values = new ArrayList<String>();
                            values.add(value);
                            parameters.put(key, values);
                        }
                    }
                }
            }
        }

        this.context = context;
        this.request = new MockHttpServletRequest("/" + context.getServletContextName(), path);
        this.request.setSession(session);
        this.response = new MockHttpServletResponse();
        setSourcePage(DEFAULT_SOURCE_PAGE);

        // Add any parameters that were embedded in the given URL
        if (parameters != null) {
            for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
                for (String value : entry.getValue()) {
                    addParameter(entry.getKey(), value);
                }
            }
        }
    }

    /** Get the servlet request object to be used by this round trip */
    public MockHttpServletRequest getRequest() {
        return request;
    }

    /** Set the servlet request object to be used by this round trip */
    protected void setRequest(MockHttpServletRequest request) {
        this.request = request;
    }

    /** Get the servlet response object to be used by this round trip */
    public MockHttpServletResponse getResponse() {
        return response;
    }

    /** Set the servlet response object to be used by this round trip */
    protected void setResponse(MockHttpServletResponse response) {
        this.response = response;
    }

    /** Get the ActionBean context to be used by this round trip */
    public MockServletContext getContext() {
        return context;
    }

    /** Set the ActionBean context to be used by this round trip */
    protected void setContext(MockServletContext context) {
        this.context = context;
    }

    /**
     * Sets the named request parameter to the value or values provided. Any existing values are
     * wiped out and replaced with the value(s) provided.
     */
    public void setParameter(String name, String... value) {
        this.request.getParameterMap().put(name, value);
    }

    /**
     * Adds the value provided to the set of values for the named request parameter. If one or
     * more values already exist they will be retained, and the new value will be appended to the
     * set of values.
     */
    public void addParameter(String name, String... value) {
        if (this.request.getParameterValues(name) == null) {
            setParameter(name, value);
        }
        else {
            String[] oldValues = this.request.getParameterMap().get(name);
            String[] combined = new String[oldValues.length + value.length];
            System.arraycopy(oldValues, 0, combined, 0, oldValues.length);
            System.arraycopy(value, 0, combined, oldValues.length, value.length);
            setParameter(name, combined);
        }
    }

    /**
     * All requests to Stripes that can generate validation errors are required to supply a
     * request parameter telling Stripes where the request came from. If you do not supply a
     * value for this parameter then the value of MockRoundTrip.DEFAULT_SOURCE_PAGE will be used.
     */
    public void setSourcePage(String url) {
        if (url != null) {
            url = CryptoUtil.encrypt(url);
        }
        setParameter(StripesConstants.URL_KEY_SOURCE_PAGE, url);
    }

    /**
     * Executes the request in the servlet context that was provided in the constructor. If the
     * request throws an Exception then that will be thrown from this method. Otherwise, once the
     * execution has completed you can use the other methods on this class to examine the outcome.
     */
    public void execute() throws Exception {
        this.context.acceptRequest(this.request, this.response);
    }

    /**
     * Executes the request in the servlet context that was provided in the constructor. Sets up
     * the request so that it mimics the submission of a specific event, named by the 'event'
     * parameter to this method. If the request throws an Exception then that will be thrown from
     * this method. Otherwise, once the execution has completed you can use the other methods on
     * this class to examine the outcome.
     */
    public void execute(String event) throws Exception {
        setParameter(event, "");
        execute();
    }

    /**
     * Gets the instance of the ActionBean type provided that was instantiated by Stripes to
     * handle the request. If a bean of this type was not instantiated, this method will
     * return null.
     *
     * @param type the Class object representing the ActionBean type expected
     * @return the instance of the ActionBean that was created by Stripes
     */
    @SuppressWarnings("unchecked")
  public <A extends ActionBean> A getActionBean(Class<A> type) {
        A bean = (A) this.request.getAttribute(getUrlBinding(type, this.context));
        if (bean == null) {
            bean = (A) this.request.getSession().getAttribute(getUrlBinding(type, this.context));
        }
        return bean;
    }

    /**
     * Gets the (potentially empty) set of Validation Errors that were produced by the request.
     */
    public ValidationErrors getValidationErrors() {
        ActionBean bean = (ActionBean) this.request.getAttribute(StripesConstants.REQ_ATTR_ACTION_BEAN);
        return bean.getContext().getValidationErrors();
    }

    /**
     * Gets, as bytes, any data that was written to the output stream associated with the
     * request. Note that since the Mock system does not write standard HTTP response information
     * (headers etc.) to the output stream, this will be exactly what was written by the
     * ActionBean.
     */
    public byte[] getOutputBytes() {
        return this.response.getOutputBytes();
    }

    /**
     * Gets, as a String, any data that was written to the output stream associated with the
     * request. Note that since the Mock system does not write standard HTTP response information
     * (headers etc.) to the output stream, this will be exactly what was written by the
     * ActionBean.
     */
    public String getOutputString() {
        return this.response.getOutputString();
    }

    /**
     * Gets the URL to which Stripes was directed after invoking the ActionBean. Assumes that
     * the request was either forwarded or redirected exactly once. If the request was forwarded
     * then the forwarded URL will be returned verbatim.  If the response was redirected and the
     * redirect URL was within the same web application, then the URL returned will exclude the
     * context path.  I.e. the URL returned will be the same regardless of whether the page was
     * forwarded to or redirected to.
     */
    public String getDestination() {
        String forward = this.request.getForwardUrl();
        String redirect = this.response.getRedirectUrl();

        if (forward != null) {
            return forward;
        }
        else if (redirect != null) {
            String contextPath = this.request.getContextPath();
            if (contextPath.length() > 1 && redirect.startsWith(contextPath + '/'))
                redirect = redirect.substring(contextPath.length());
        }

        return redirect;
    }

    /** If the request resulted in a forward, returns the URL that was forwarded to. */
    public String getForwardUrl() {
        return this.request.getForwardUrl();
    }

    /**
     * If the request resulted in a redirect, returns the URL that was redirected to. Unlike
     * getDestination(), the URL in this case will be the exact URL that would have been sent to
     * the browser (i.e. including the servlet context).
     */
    public String getRedirectUrl() {
        return this.response.getRedirectUrl();
    }

    /** Find and return the {@link AnnotatedClassActionResolver} for the given context. */
    private static AnnotatedClassActionResolver getActionResolver(MockServletContext context) {
        for (Filter filter : context.getFilters()) {
            if (filter instanceof StripesFilter) {
                ActionResolver resolver = ((StripesFilter) filter).getInstanceConfiguration()
                        .getActionResolver();
                if (resolver instanceof AnnotatedClassActionResolver) {
                    return (AnnotatedClassActionResolver) resolver;
                }
            }
        }

        return null;
    }

    /** Find and return the {@link UrlBindingFactory} for the given context. */
    private static UrlBindingFactory getUrlBindingFactory(MockServletContext context) {
        ActionResolver resolver = getActionResolver(context);
        if (resolver instanceof AnnotatedClassActionResolver) {
            return ((AnnotatedClassActionResolver) resolver).getUrlBindingFactory();
        }

        return null;
    }

    /**
     * A helper method that fetches the UrlBinding of a class in the manner it would be interpreted
     * by the current context configuration.
     */
    private static String getUrlBinding(Class<? extends ActionBean> clazz,
            MockServletContext context) {
        return getActionResolver(context).getUrlBinding(clazz);
    }

    /** Get the URL binding for an {@link ActionBean} class up to the first parameter. */
    private static String getUrlBindingStub(Class<? extends ActionBean> clazz,
            MockServletContext context) {
        return getUrlBindingFactory(context).getBindingPrototype(clazz).getPath();
    }
}
TOP

Related Classes of net.sourceforge.stripes.mock.MockRoundtrip

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.