org.geoserver.test.GeoServerAbstractTestSupport.java Source code

Java tutorial

Introduction

Here is the source code for org.geoserver.test.GeoServerAbstractTestSupport.java

Source

/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.test;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import net.sf.json.JSON;
import net.sf.json.JSONSerializer;

import org.apache.commons.codec.binary.Base64;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSchemaDirective;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.config.GeoServerLoader;
import org.geoserver.config.GeoServerLoaderProxy;
import org.geoserver.data.test.MockData;
import org.geoserver.data.test.TestData;
import org.geoserver.logging.LoggingUtils;
import org.geoserver.ows.util.KvpUtils;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.ContextLoadedEvent;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.security.GeoServerSecurityManager;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.Hints;
import org.geotools.referencing.CRS;
import org.geotools.util.logging.Log4JLoggerFactory;
import org.geotools.util.logging.Logging;
import org.geotools.xml.XSD;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.mockrunner.mock.web.MockFilterChain;
import com.mockrunner.mock.web.MockHttpServletRequest;
import com.mockrunner.mock.web.MockHttpServletResponse;
import com.mockrunner.mock.web.MockHttpSession;
import com.mockrunner.mock.web.MockServletConfig;
import com.mockrunner.mock.web.MockServletContext;
import com.mockrunner.mock.web.MockServletOutputStream;

/**
 * Base test class for GeoServer unit tests.
 * <p>
 * This test case provides a spring application context which loads the
 * application contexts from all modules on the classpath.
 * </p>
 * <p>
 * Subclasses should provide a data directory location, that will be inserted
 * in the mock servlet context for GeoServer to pick up
 * </p>
 * @author Justin Deoliveira, The Open Planning Project, jdeolive@openplans.org
 * @author Andrea Aime, The Open Planning Project
 */
public abstract class GeoServerAbstractTestSupport extends OneTimeSetupTest {
    /**
     * Common logger for test cases
     */
    protected static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geoserver.test");

    /**
     * Application context
     */
    protected static GeoServerTestApplicationContext applicationContext;

    protected static TestData testData;

    String username;

    String password;

    /**
     * Returns a test data instance
     * 
     * @return
     */
    protected abstract TestData buildTestData() throws Exception;

    public TestData getTestData() {
        return testData;
    }

    /**
     * Override runTest so that the test will be skipped if the TestData is not
     * available
     */
    protected void runTest() throws Throwable {
        if (getTestData().isTestDataAvailable()) {
            super.runTest();
        } else {
            LOGGER.warning("Skipping " + getClass() + "." + getName() + " since test data is not available");
        }
    }

    @Override
    protected void tearDownInternal() throws Exception {
        super.tearDownInternal();
        username = null;
        password = null;
    }

    /**
     * If subclasses override they *must* call super.setUp() first.
     */
    @Override
    protected void oneTimeSetUp() throws Exception {
        // do we need to reset the referencing subsystem and reorient it with lon/lat order?
        if (System.getProperty("org.geotools.referencing.forceXY") == null
                || !"http".equals(Hints.getSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING))) {
            System.setProperty("org.geotools.referencing.forceXY", "true");
            Hints.putSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING, "http");
            CRS.reset("all");
        }

        // reset security services
        //GeoserverServiceFactory.Singleton.reset();

        // set up test data 
        testData = buildTestData();
        testData.setUp();

        // setup quiet logging (we need to to this here because Data
        // is loaded before GoeServer has a chance to setup logging for good)
        try {
            Logging.ALL.setLoggerFactory(Log4JLoggerFactory.getInstance());
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, "Could not configure log4j logging redirection", e);
        }
        System.setProperty(LoggingUtils.RELINQUISH_LOG4J_CONTROL, "true");
        GeoServerResourceLoader loader = new GeoServerResourceLoader(testData.getDataDirectoryRoot());
        LoggingUtils.configureGeoServerLogging(loader, getClass().getResourceAsStream(getLogConfiguration()), false,
                true, null);

        //HACK: once we port tests to the new data directory, remove this
        GeoServerLoader.setLegacy(useLegacyDataDirectory());

        // if we have data, create a mock servlet context and start up the spring configuration
        if (testData.isTestDataAvailable()) {
            MockServletContext servletContext = new MockServletContext();
            servletContext.setInitParameter("GEOSERVER_DATA_DIR", testData.getDataDirectoryRoot().getPath());
            servletContext.setInitParameter("serviceStrategy", "PARTIAL-BUFFER2");

            //set up a fake WEB-INF directory
            if (testData.getDataDirectoryRoot().canWrite()) {
                File webinf = new File(testData.getDataDirectoryRoot(), "WEB-INF");
                webinf.mkdir();

                servletContext.setRealPath("WEB-INF", webinf.getAbsolutePath());
            }

            applicationContext = new GeoServerTestApplicationContext(getSpringContextLocations(), servletContext);
            applicationContext.setUseLegacyGeoServerLoader(useLegacyDataDirectory());
            applicationContext.refresh();
            applicationContext.publishEvent(new ContextLoadedEvent(applicationContext));

            // set the parameter after a refresh because it appears a refresh
            // wipes
            // out all parameters
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
                    applicationContext);
        }
    }

    /**
     * Flag which controls the mock data directory setup.
     * <p>
     * If true is returned, the legacy structure is presevered on sstartup, and no 
     * conversion to the new data directory structure happens.
     * </p>
     * 
     */
    protected boolean useLegacyDataDirectory() {
        return true;
    }

    /**
     * Returns the spring context locations to be used in order to build the GeoServer Spring
     * context. Subclasses might want to provide extra locations in order to test extension points.
     * @return
     */
    protected String[] getSpringContextLocations() {
        return new String[] { "classpath*:/applicationContext.xml", "classpath*:/applicationSecurityContext.xml" };
    }

    /**
     * Returns the logging configuration path. The default value is "/TEST_LOGGING.properties", which
     * is a pretty quiet configuration. Should you need more verbose logging override this method
     * in subclasses and choose a different configuration, for example "/DEFAULT_LOGGING.properties".
     * @return
     */
    protected String getLogConfiguration() {
        return "/TEST_LOGGING.properties";
    }

    /**
     * Returns a default services.xml file with WMS, WFS and WCS enabled. Subclasses may
     * need to override this in order to test extra services or specific configurations
     * @return
     */
    protected URL getServicesFile() {
        return GeoServerAbstractTestSupport.class.getResource("services.xml");
    }

    /**
     * Subclasses may override this method to force memory cleaning before the 
     * test data dir is cleaned up. This is necessary on windows if coverages are used in the
     * test, since readers might still be around in the heap as garbage without having
     * been disposed of
     * @return
     */
    protected boolean isMemoryCleanRequired() {
        return false;
    }

    /**
     * If subclasses overide they *must* call super.tearDown() first.
     */
    @Override
    protected void oneTimeTearDown() throws Exception {
        if (getTestData().isTestDataAvailable()) {
            try {
                //dispose WFS XSD schema's - they will otherwise keep geoserver instance alive forever!!
                disposeIfExists(getXSD11());
                disposeIfExists(getXSD10());

                // kill the context
                applicationContext.destroy();

                // kill static caches
                new GeoServerExtensions().setApplicationContext(null);

                // some tests do need a kick on the GC to fully clean up
                if (isMemoryCleanRequired()) {
                    System.gc();
                    System.runFinalization();
                }

                if (getTestData() != null) {
                    // this cleans up the data directory static loader, if we don't the next test
                    // will keep on running on the current data dir
                    GeoserverDataDirectory.destroy();
                    getTestData().tearDown();
                }
            } finally {
                applicationContext = null;
                testData = null;
            }
        }
    }

    /**
     * Reloads the catalog and configuration from disk.
     * <p>
     * This method can be used by subclasses from a test method after they have
     * changed the configuration on disk.
     * </p>
     */
    protected void reloadCatalogAndConfiguration() throws Exception {
        GeoServerLoaderProxy loader = GeoServerExtensions.bean(GeoServerLoaderProxy.class, applicationContext);
        loader.reload();
    }

    /**
     * Accessor for global catalog instance from the test application context.
     */
    protected Catalog getCatalog() {
        return (Catalog) applicationContext.getBean("catalog");
    }

    /**
     * Accessor for global geoserver instance from the test application context.
     */
    protected GeoServer getGeoServer() {
        return (GeoServer) applicationContext.getBean("geoServer");
    }

    /**
     * Accesssor for global security manager instance from the test application context.
     */
    protected GeoServerSecurityManager getSecurityManager() {
        return (GeoServerSecurityManager) applicationContext.getBean("geoServerSecurityManager");
    }

    /**
     * Flush XSD if exists.
     */
    protected static void disposeIfExists(XSD xsd) {
        if (xsd != null) {
            xsd.dispose();
        }
    }

    /**
     * Accessor for WFS 1.0 XSD from the test application context.
     */
    protected XSD getXSD11() {
        if (applicationContext.containsBean("wfsXsd-1.1")) {
            return (XSD) applicationContext.getBean("wfsXsd-1.1");
        } else {
            return null;
        }
    }

    /**
     * Accessor for WFS 1.0 XSD from the test application context.
     */
    protected XSD getXSD10() {
        if (applicationContext.containsBean("wfsXsd-1.0")) {
            return (XSD) applicationContext.getBean("wfsXsd-1.0");
        } else {
            return null;
        }
    }

    /**
     * Accessor for global resource loader instance from the test application context.
     */
    protected GeoServerResourceLoader getResourceLoader() {
        return (GeoServerResourceLoader) applicationContext.getBean("resourceLoader");
    }

    protected GeoServerDataDirectory getDataDirectory() {
        return new GeoServerDataDirectory(getResourceLoader());
    }

    /**
     * Loads a feature source from the catalog.
     *
     * @param typeName The qualified type name of the feature source.
     */
    @SuppressWarnings("unchecked")
    protected SimpleFeatureSource getFeatureSource(QName typeName) throws IOException {
        // TODO: expand test support to DataAccess FeatureSource
        FeatureTypeInfo ft = getFeatureTypeInfo(typeName);
        return DataUtilities.simple((FeatureSource) ft.getFeatureSource(null, null));
    }

    /**
     * Get the FeatureTypeInfo for a featuretype to allow configuration tweaks for tests.
     *
     * @param typename the QName for the type
     */
    protected FeatureTypeInfo getFeatureTypeInfo(QName typename) {
        return getCatalog().getFeatureTypeByName(typename.getNamespaceURI(), typename.getLocalPart());
    }

    /**
     * Sets the authentication for this test run (will be removed during {@link #tearDownInternal()}
     * ). Use a null user name to turn off authentication again.
     * <p>
     * Remember to override the getFilters() method so that Spring Security filters are enabled
     * during testing (otherwise no authentication will take place):
     * 
     * <pre>
     * protected List&lt;javax.servlet.Filter&gt; getFilters() {
     *     return Collections.singletonList((javax.servlet.Filter) GeoServerExtensions
     *             .bean(&quot;filterChainProxy&quot;));
     * }
     * </pre>
     * <p>
     * Also remember to add the users in the user.properties file, for example:
     * 
     * <pre>
     * protected void populateDataDirectory(MockData dataDirectory) throws Exception {
     *     super.populateDataDirectory(dataDirectory);
     *     File security = new File(dataDirectory.getDataDirectoryRoot(), &quot;security&quot;);
     *     security.mkdir();
     * 
     *     File users = new File(security, &quot;users.properties&quot;);
     *     Properties props = new Properties();
     *     props.put(&quot;admin&quot;, &quot;geoserver,ROLE_ADMINISTRATOR&quot;);
     *     props.store(new FileOutputStream(users), &quot;&quot;);
     * }
     * </pre>
     * 
     * @param username
     * @param password
     */
    protected void authenticate(String username, String password) {
        this.username = username;
        this.password = password;
    }

    /**
     * Get the FeatureTypeInfo for a featuretype by the layername that would be used in a request.
     *
     * @param typename the layer name for the type
     */
    protected FeatureTypeInfo getFeatureTypeInfo(String typename) {
        return getFeatureTypeInfo(resolveLayerName(typename));
    }

    /**
     * Get the QName for a layer specified by the layername that would be used in a request.
     * @param typename the layer name for the type
     */
    protected QName resolveLayerName(String typename) {
        int i = typename.indexOf(":");
        String prefix = typename.substring(0, i);
        String name = typename.substring(i + 1);
        NamespaceInfo ns = getCatalog().getNamespaceByPrefix(prefix);
        QName qname = new QName(ns.getURI(), name, ns.getPrefix());
        return qname;
    }

    /**
     * Given a qualified layer name returns a string in the form "prefix:localPart" if prefix
     * is available, "localPart" if prefix is null
     * @param layerName
     * @return
     */
    public String getLayerId(QName layerName) {
        if (layerName.getPrefix() != null)
            return layerName.getPrefix() + ":" + layerName.getLocalPart();
        else
            return layerName.getLocalPart();
    }

    /**
     * Convenience method for subclasses to create mock http servlet requests.
     * <p>
     * Examples of using this method are:
     * <pre>
     * <code>
     *   createRequest( "wfs?request=GetCapabilities" );  //get
     *   createRequest( "wfs" ); //post
     * </code>
     * </pre>
     * </p>
     * @param path The path for the request and optional the query string.
     * @return
     */
    protected MockHttpServletRequest createRequest(String path) {
        MockHttpServletRequest request = new GeoServerMockHttpServletRequest();

        request.setScheme("http");
        request.setServerName("localhost");
        request.setContextPath("/geoserver");
        request.setRequestURI(ResponseUtils.stripQueryString(ResponseUtils.appendPath("/geoserver/", path)));
        request.setRequestURL(ResponseUtils.appendPath("http://localhost/geoserver", path));
        request.setQueryString(ResponseUtils.getQueryString(path));
        request.setRemoteAddr("127.0.0.1");
        request.setServletPath(ResponseUtils.makePathAbsolute(ResponseUtils.stripRemainingPath(path)));
        request.setPathInfo(ResponseUtils.makePathAbsolute(ResponseUtils.stripBeginningPath(path)));

        // deal with authentication
        if (username != null) {
            String token = username + ":";
            if (password != null) {
                token += password;
            }
            request.addHeader("Authorization", "Basic " + new String(Base64.encodeBase64(token.getBytes())));
        }

        kvp(request, path);

        MockHttpSession session = new MockHttpSession();
        session.setupServletContext(new MockServletContext());
        request.setSession(session);

        request.setUserPrincipal(null);

        return request;
    }

    /**
     * Convenience method for subclasses to create mock http servlet requests.
     * <p>
     * Examples of using this method are:
     * <pre>
     * <code>
     *   Map kvp = new HashMap();
     *   kvp.put( "service", "wfs" );
     *   kvp.put( "request", "GetCapabilities" );
     *   
     *   createRequest( "wfs", kvp );
     * </code>
     * </pre>
     * </p>
     * @param path The path for the request, minus any query string parameters.
     * @param kvp The key value pairs to be put in teh query string. 
     * 
     */
    protected MockHttpServletRequest createRequest(String path, Map kvp) {
        StringBuffer q = new StringBuffer();
        for (Iterator e = kvp.entrySet().iterator(); e.hasNext();) {
            Map.Entry entry = (Map.Entry) e.next();
            q.append(entry.getKey()).append("=").append(entry.getValue());
            q.append("&");
        }
        q.setLength(q.length() - 1);

        return createRequest(ResponseUtils.appendQueryString(path, q.toString()));
    }

    /**
     * Executes an ows request using the GET method.
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected InputStream get(String path) throws Exception {
        MockHttpServletResponse response = getAsServletResponse(path);
        return new ByteArrayInputStream(response.getOutputStreamContent().getBytes());
    }

    /**
     * Executes an ows request using the GET method.
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * 
     * @return the mock servlet response
     * 
     * @throws Exception
     */
    protected MockHttpServletResponse getAsServletResponse(String path) throws Exception {
        return getAsServletResponse(path, null);
    }

    /**
     * Executes an ows request using the GET method.
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * @param charset The character set of the response.
     * 
     * @return the mock servlet response
     */
    protected MockHttpServletResponse getAsServletResponse(String path, String charset) throws Exception {
        MockHttpServletRequest request = createRequest(path);
        request.setMethod("GET");
        request.setBodyContent(new byte[] {});

        return dispatch(request, charset);
    }

    /**
     * Executes an ows request using the POST method with key value pairs 
     * form encoded. 
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected InputStream post(String path) throws Exception {
        MockHttpServletRequest request = createRequest(path);
        request.setMethod("POST");
        request.setContentType("application/x-www-form-urlencoded");
        request.setBodyContent(new byte[] {});

        MockHttpServletResponse response = dispatch(request);
        return new ByteArrayInputStream(response.getOutputStreamContent().getBytes());
    }

    /**
     * Executes a request with an empty body using the PUT method.
     *
     * @param path the portion of the request after the context, for example:
     *      "api/datastores.xml"
     *
     * @throws Exception
     */
    protected InputStream put(String path) throws Exception {
        return put(path, "");
    }

    /**
     * Executes a request with a default mimetype using the PUT method.
     *
     * @param path the portion of the request after the context, for example:
     *      "api/datastores.xml"
     * @param body the content to send as the body of the request
     *
     * @throws Exception
     */
    protected InputStream put(String path, String body) throws Exception {
        return put(path, body, "text/plain");
    }

    /**
     * Executes a request using the PUT method.
     *
     * @param path the portion of the request after the context, for example:
     *      "api/datastores.xml"
     * @param body the content to send as the body of the request
     * @param contentType the mime-type to set for the request being sent
     *
     * @throws Exception
     */
    protected InputStream put(String path, String body, String contentType) throws Exception {
        return put(path, body.getBytes(), contentType);
    }

    /**
     * Executes a request using the PUT method.
     *
     * @param path the portion of the request after the context, for example:
     *      "api/datastores.xml"
     * @param body the content to send as the body of the request
     * @param contentType the mime-type to set for the request being sent
     *
     * @throws Exception
     */
    protected InputStream put(String path, byte[] body, String contentType) throws Exception {
        MockHttpServletResponse response = putAsServletResponse(path, body, contentType);
        return new ByteArrayInputStream(response.getOutputStreamContent().getBytes());
    }

    protected MockHttpServletResponse putAsServletResponse(String path) throws Exception {
        return putAsServletResponse(path, new byte[] {}, "text/plain");
    }

    protected MockHttpServletResponse putAsServletResponse(String path, String body, String contentType)
            throws Exception {
        return putAsServletResponse(path, body != null ? body.getBytes() : (byte[]) null, contentType);
    }

    protected MockHttpServletResponse putAsServletResponse(String path, byte[] body, String contentType)
            throws Exception {

        MockHttpServletRequest request = createRequest(path);
        request.setMethod("PUT");
        request.setContentType(contentType);
        request.setBodyContent(body);
        request.setHeader("Content-type", contentType);

        return dispatch(request);
    }

    /**
     * Executes an ows request using the POST method.
     * <p>
     * 
     * </p>
     * @param path The porition of the request after the context ( no query string ), 
     *      example: 'wms'. 
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected InputStream post(String path, String xml) throws Exception {
        MockHttpServletResponse response = postAsServletResponse(path, xml);
        return new ByteArrayInputStream(response.getOutputStreamContent().getBytes());
    }

    /**
     * Executes an ows request using the POST method, with xml as body content. 
     * 
     * 
     * @param path
     *            The porition of the request after the context ( no query
     *            string ), example: 'wms'.
     * @param xml The body content.
     * 
     * @return the servlet response
     * 
     * @throws Exception
     */
    protected MockHttpServletResponse postAsServletResponse(String path, String xml) throws Exception {

        return postAsServletResponse(path, xml, "application/xml");
    }

    /**
     * Extracts the true binary stream out of the response. The usual way (going
     * thru {@link MockHttpServletResponse#getOutputStreamContent()}) mangles
     * bytes if the content is not made of chars.
     * 
     * @param response
     * @return
     */
    protected ByteArrayInputStream getBinaryInputStream(MockHttpServletResponse response) {
        try {
            MockServletOutputStream os = (MockServletOutputStream) response.getOutputStream();
            final Field field = os.getClass().getDeclaredField("buffer");
            field.setAccessible(true);
            ByteArrayOutputStream bos = (ByteArrayOutputStream) field.get(os);
            return new ByteArrayInputStream(bos.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException("Whoops, did you change the MockRunner version? "
                    + "If so, you might want to change this method too");
        }
    }

    /**
     * Executes an ows request using the POST method.
     * 
     * @param path
     *            The porition of the request after the context ( no query
     *            string ), example: 'wms'.
     * 
     * @param body
     *            the body of the request
     * @param contentType
     *            the mimetype to set for the request
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected InputStream post(String path, String body, String contentType) throws Exception {
        MockHttpServletResponse response = postAsServletResponse(path, body, contentType);
        return new ByteArrayInputStream(response.getOutputStreamContent().getBytes());
    }

    protected MockHttpServletResponse postAsServletResponse(String path, String body, String contentType)
            throws Exception {
        MockHttpServletRequest request = createRequest(path);
        request.setMethod("POST");
        request.setContentType(contentType);
        request.setBodyContent(body);
        request.setHeader("Content-type", contentType);

        return dispatch(request);
    }

    /**
     * Execultes a request using the DELETE method.
     * 
     * @param path The path of the request.
     * 
     * @return The http status code.
     */
    protected MockHttpServletResponse deleteAsServletResponse(String path) throws Exception {
        MockHttpServletRequest request = createRequest(path);
        request.setMethod("DELETE");

        return dispatch(request);
    }

    /**
     * Executes an ows request using the GET method and returns the result as an 
     * xml document.
     * 
     * @param path The portion of the request after the context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * @param the list of validation errors encountered during document parsing (validation
     *        will be activated only if this list is non null)
     * 
     * @return A result of the request parsed into a dom.
     * 
     * @throws Exception
     */
    protected Document getAsDOM(final String path) throws Exception {
        return getAsDOM(path, true);
    }

    /**
     * Executes a request using the GET method and parses the result as a json object.
     * 
     * @param path The path to request.
     *  
     * @return The result parsed as json.
     */
    protected JSON getAsJSON(final String path) throws Exception {
        MockHttpServletResponse response = getAsServletResponse(path);
        return json(response);
    }

    protected JSON json(MockHttpServletResponse response) {
        String content = response.getOutputStreamContent();
        return JSONSerializer.toJSON(content);
    }

    /**
     * Executes an ows request using the GET method and returns the result as an xml document.
     * 
     * @param path
     *                The portion of the request after the context, example:
     *                'wms?request=GetMap&version=1.1.1&..."
     * @param skipDTD
     *                if true, will avoid loading and validating against the response document
     *                schema or DTD
     * 
     * @return A result of the request parsed into a dom.
     * 
     * @throws Exception
     */
    protected Document getAsDOM(final String path, final boolean skipDTD) throws Exception {
        return dom(get(path), skipDTD);
    }

    /**
     * Executes an ows request using the POST method with key value pairs 
     * form encoded, returning the result as a dom.
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * @param the list of validation errors encountered during document parsing (validation
     *        will be activated only if this list is non null)     
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected Document postAsDOM(String path) throws Exception {
        return postAsDOM(path, (List<Exception>) null);
    }

    /**
     * Executes an ows request using the POST method with key value pairs 
     * form encoded, returning the result as a dom.
     *
     * @param path The porition of the request after hte context, 
     *      example: 'wms?request=GetMap&version=1.1.1&..."
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected Document postAsDOM(String path, List<Exception> validationErrors) throws Exception {
        return dom(post(path));
    }

    /**
     * Executes an ows request using the POST method and returns the result as an
     * xml document.
     * <p>
     * 
     * </p>
     * @param path The porition of the request after the context ( no query string ), 
     *      example: 'wms'. 
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected Document postAsDOM(String path, String xml) throws Exception {
        return postAsDOM(path, xml, null);
    }

    /**
     * Executes an ows request using the POST method and returns the result as an
     * xml document.
     * <p>
     * 
     * </p>
     * @param path The porition of the request after the context ( no query string ), 
     *      example: 'wms'. 
     * 
     * @return An input stream which is the result of the request.
     * 
     * @throws Exception
     */
    protected Document postAsDOM(String path, String xml, List<Exception> validationErrors) throws Exception {
        return dom(post(path, xml));
    }

    protected String getAsString(String path) throws Exception {
        return string(get(path));
    }

    /**
     * Parses a stream into a dom.
     */
    protected Document dom(InputStream is) throws ParserConfigurationException, SAXException, IOException {
        return dom(is, true);
    }

    /**
     * Parses a stream into a dom.
     * @param input
     * @param skipDTD If true, will skip loading and validating against the associated DTD
     */
    protected Document dom(InputStream input, boolean skipDTD)
            throws ParserConfigurationException, SAXException, IOException {
        if (skipDTD) {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            factory.setValidating(false);

            DocumentBuilder builder = factory.newDocumentBuilder();
            builder.setEntityResolver(new EmptyResolver());
            Document dom = builder.parse(input);

            return dom;
        } else {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = factory.newDocumentBuilder();
            return builder.parse(input);
        }
    }

    /**
     * Resolves everything to an empty xml document, useful for skipping errors due to missing
     * dtds and the like
     * @author Andrea Aime - TOPP
     */
    static class EmptyResolver implements org.xml.sax.EntityResolver {
        public InputSource resolveEntity(String publicId, String systemId)
                throws org.xml.sax.SAXException, IOException {
            StringReader reader = new StringReader("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            InputSource source = new InputSource(reader);
            source.setPublicId(publicId);
            source.setSystemId(systemId);

            return source;
        }
    }

    protected void checkValidationErorrs(Document dom, String schemaLocation) throws SAXException, IOException {
        final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = factory.newSchema(new File(schemaLocation));
        checkValidationErrors(dom, schema);
    }

    /**
     * Given a dom and a schema, checks that the dom validates against the schema 
     * of the validation errors instead
     * @param validationErrors
     * @throws IOException 
     * @throws SAXException 
     */
    protected void checkValidationErrors(Document dom, Schema schema) throws SAXException, IOException {
        final Validator validator = schema.newValidator();
        final List<Exception> validationErrors = new ArrayList<Exception>();
        validator.setErrorHandler(new ErrorHandler() {

            public void warning(SAXParseException exception) throws SAXException {
                System.out.println(exception.getMessage());
            }

            public void fatalError(SAXParseException exception) throws SAXException {
                validationErrors.add(exception);
            }

            public void error(SAXParseException exception) throws SAXException {
                validationErrors.add(exception);
            }

        });
        validator.validate(new DOMSource(dom));
        if (validationErrors != null && validationErrors.size() > 0) {
            StringBuilder sb = new StringBuilder();
            for (Exception ve : validationErrors) {
                sb.append(ve.getMessage()).append("\n");
            }
            fail(sb.toString());
        }
    }

    /**
     * Performs basic checks on an OWS 1.0 exception, to ensure it's well formed
     */
    protected void checkOws10Exception(Document dom) throws Exception {
        checkOws10Exception(dom, null, null);
    }

    /**
     * Performs basic checks on an OWS 1.0 exception, to ensure it's well formed
     * and ensuring that a particular exceptionCode is used.
     */
    protected void checkOws10Exception(Document dom, String exceptionCode) throws Exception {
        checkOws10Exception(dom, exceptionCode, null);
    }

    /**
     * Performs basic checks on an OWS 1.0 exception, to ensure it's well formed
     * and ensuring that a particular exceptionCode is used.
     */
    protected void checkOws10Exception(Document dom, String exceptionCode, String locator) throws Exception {
        Element root = dom.getDocumentElement();
        assertEquals("ows:ExceptionReport", root.getNodeName());
        assertEquals("1.0.0", root.getAttribute("version"));
        assertEquals("http://www.opengis.net/ows", root.getAttribute("xmlns:ows"));
        assertEquals(1, dom.getElementsByTagName("ows:Exception").getLength());

        Element ex = (Element) dom.getElementsByTagName("ows:Exception").item(0);
        if (exceptionCode != null) {
            assertEquals(exceptionCode, ex.getAttribute("exceptionCode"));
        }
        if (locator != null) {
            assertEquals(locator, ex.getAttribute("locator"));
        }
    }

    /**
     * Performs basic checks on an OWS 1.1 exception, to ensure it's well formed
     */
    protected void checkOws11Exception(Document dom) throws Exception {
        checkOws11Exception(dom, null);
    }

    /**
     * Performs basic checks on an OWS 1.1 exception, to ensure it's well formed
     * and ensuring that a particular exceptionCode is used.
     */
    protected void checkOws11Exception(Document dom, String exceptionCode) throws Exception {
        Element root = dom.getDocumentElement();
        assertEquals("ows:ExceptionReport", root.getNodeName());
        assertEquals("1.1.0", root.getAttribute("version"));
        assertEquals("http://www.opengis.net/ows/1.1", root.getAttribute("xmlns:ows"));

        if (exceptionCode != null) {
            assertEquals(1, dom.getElementsByTagName("ows:Exception").getLength());
            Element ex = (Element) dom.getElementsByTagName("ows:Exception").item(0);
            assertEquals(exceptionCode, ex.getAttribute("exceptionCode"));
        }
    }

    /**
     * Parses a stream into a String
     */
    protected String string(InputStream input) throws Exception {
        BufferedReader reader = null;
        StringBuffer sb = new StringBuffer();
        char[] buf = new char[8192];
        try {
            reader = new BufferedReader(new InputStreamReader(input));
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
        } finally {
            if (reader != null)
                reader.close();
        }
        return sb.toString();
    }

    /**
     * Utility method to print out a dom.
     */
    protected void print(Document dom) throws Exception {
        TransformerFactory txFactory = TransformerFactory.newInstance();
        try {
            txFactory.setAttribute("{http://xml.apache.org/xalan}indent-number", new Integer(2));
        } catch (Exception e) {
            // some 
        }

        Transformer tx = txFactory.newTransformer();
        tx.setOutputProperty(OutputKeys.METHOD, "xml");
        tx.setOutputProperty(OutputKeys.INDENT, "yes");

        tx.transform(new DOMSource(dom), new StreamResult(new OutputStreamWriter(System.out, "utf-8")));
    }

    /**
     * Utility method to print out the contents of an input stream.
     */
    protected void print(InputStream in) throws Exception {
        BufferedReader r = new BufferedReader(new InputStreamReader(in));
        String line = null;
        while ((line = r.readLine()) != null) {
            System.out.println(line);
        }
    }

    /**
     * Utility method to print out the contents of a json object.
     */
    protected void print(JSON json) {
        System.out.println(json.toString(2));
    }

    /**
     * Convenience method for element.getElementsByTagName() to return the 
     * first element in the resulting node list.
     */
    protected Element getFirstElementByTagName(Element element, String name) {
        NodeList elements = element.getElementsByTagName(name);
        if (elements.getLength() > 0) {
            return (Element) elements.item(0);
        }

        return null;
    }

    /**
     * Convenience method for element.getElementsByTagName() to return the 
     * first element in the resulting node list.
     */
    protected Element getFirstElementByTagName(Document dom, String name) {
        return getFirstElementByTagName(dom.getDocumentElement(), name);
    }

    /*
     * Helper method to create the kvp params from the query string.
     */
    private void kvp(MockHttpServletRequest request, String path) {
        Map<String, Object> params = KvpUtils.parseQueryString(path);
        for (String key : params.keySet()) {
            Object value = params.get(key);
            if (value instanceof String) {
                request.setupAddParameter(key, (String) value);
            } else {
                String[] values = (String[]) value;
                for (String v : values) {
                    request.setupAddParameter(key, v);
                }
            }

        }

    }

    protected MockHttpServletResponse dispatch(HttpServletRequest request) throws Exception {
        return dispatch(request, (String) null);
    }

    protected MockHttpServletResponse dispatch(HttpServletRequest request, String charset) throws Exception {
        MockHttpServletResponse response = null;
        if (charset == null) {
            response = new MockHttpServletResponse() {
                public void setCharacterEncoding(String encoding) {

                }
            };
        } else {
            response = new MockHttpServletResponse();
            response.setCharacterEncoding(charset);
        }

        dispatch(request, response);
        return response;
    }

    protected DispatcherServlet getDispatcher() throws Exception {
        // create an instance of the spring dispatcher
        ServletContext context = applicationContext.getServletContext();

        MockServletConfig config = new MockServletConfig();
        config.setServletContext(context);
        config.setServletName("dispatcher");

        DispatcherServlet dispatcher = new DispatcherServlet();

        dispatcher.setContextConfigLocation(
                GeoServerAbstractTestSupport.class.getResource("dispatcher-servlet.xml").toString());
        dispatcher.init(config);

        return dispatcher;
    }

    private void dispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        final DispatcherServlet dispatcher = getDispatcher();

        // build a filter chain so that we can test with filters as well
        MockFilterChain chain = new MockFilterChain();
        List<Filter> filters = getFilters();
        if (filters != null) {
            for (Filter filter : filters) {
                chain.addFilter(filter);
            }
        }
        chain.setServlet(new HttpServlet() {
            @Override
            protected void service(HttpServletRequest request, HttpServletResponse response)
                    throws ServletException, IOException {
                try {
                    //excute the pre handler step
                    Collection interceptors = GeoServerExtensions.extensions(HandlerInterceptor.class,
                            applicationContext);
                    for (Iterator i = interceptors.iterator(); i.hasNext();) {
                        HandlerInterceptor interceptor = (HandlerInterceptor) i.next();
                        interceptor.preHandle(request, response, dispatcher);
                    }

                    //execute 
                    //dispatcher.handleRequest( request, response );
                    dispatcher.service(request, response);

                    //execute the post handler step
                    for (Iterator i = interceptors.iterator(); i.hasNext();) {
                        HandlerInterceptor interceptor = (HandlerInterceptor) i.next();
                        interceptor.postHandle(request, response, dispatcher, null);
                    }
                } catch (RuntimeException e) {
                    throw e;
                } catch (IOException e) {
                    throw e;
                } catch (ServletException e) {
                    throw e;
                } catch (Exception e) {
                    throw (IOException) new IOException("Failed to handle the request").initCause(e);
                }
            }
        });

        chain.doFilter(request, response);

    }

    //    private DispatcherServlet getDispatcher() throws ServletException {
    //        if(dispatcher == null) {
    //            synchronized (this) {
    //                if(dispatcher == null) {
    //                    
    //                }
    //            }
    //        }
    //        return dispatcher;
    //    }

    /**
     * Subclasses needed to do integration tests with servlet filters can override this method
     * and return the list of filters to be used during mocked requests
     * @return
     */
    protected List<Filter> getFilters() {
        return null;
    }

    /**
     * Assert that a GET request to a path will have a particular status code for the response.
     * @param code the number of the HTTP status code that is expected
     * @param path the path to which a GET request should be made, without the protocol, server and servlet context.
     * For example, to make a request to "http://localhost:8080/geoserver/ows" the path would be "ows"
     *
     * @throws Exception on test failure
     */
    protected void assertStatusCodeForGet(int code, String path) throws Exception {
        assertStatusCodeForRequest(code, "GET", path, "", "");
    }

    /**
     * Assert that a POST request to a path will have a particular status code for the response.
     * @param code the number of the HTTP status code that is expected
     * @param path the path to which a POST request should be made, without the protocol, server and servlet context.
     * For example, to make a request to "http://localhost:8080/geoserver/ows" the path would be "ows"
     * @param body the body to send with the request. May be empty, but must not be null.
     * @param type the mimetype to report for the body
     *
     * @throws Exception on test failure
     */
    protected void assertStatusCodeForPost(int code, String path, String body, String type) throws Exception {
        assertStatusCodeForRequest(code, "POST", path, body, type);
    }

    /**
     * Assert that a PUT request to a path will have a particular status code for the response.
     * @param code the number of the HTTP status code that is expected
     * @param path the path to which a PUT request should be made, without the protocol, server and servlet context.
     * For example, to make a request to "http://localhost:8080/geoserver/ows" the path would be "ows"
     * @param body the body to send with the request. May be empty, but must not be null.
     * @param type the mimetype to report for the body
     *
     * @throws Exception on test failure
     */
    protected void assertStatusCodeForPut(int code, String path, String body, String type) throws Exception {
        assertStatusCodeForRequest(code, "PUT", path, body, type);
    }

    /**
     * Assert that an HTTP request will have a particular status code for the response.
     * @param code the number of the HTTP status code that is expected
     * @param method the HTTP method for the request (eg, GET, PUT)
     * @param path the path for the request, excluding the protocol, server, port, and servlet context.
     * For example, to make a request to "http://localhost:8080/geoserver/ows" the path would be "ows"
     * @param body the body for the request.  May be empty, but must not be null.
     * @param type the mimetype for the request.
     */
    protected void assertStatusCodeForRequest(int code, String method, String path, String body, String type)
            throws Exception {
        MockHttpServletRequest request = createRequest(path);
        request.setMethod(method);
        request.setBodyContent(body);
        request.setContentType(type);

        CodeExpectingHttpServletResponse response = new CodeExpectingHttpServletResponse(
                new MockHttpServletResponse());
        dispatch(request, response);
        assertEquals(code, response.getErrorCode());
    }

    public static class GeoServerMockHttpServletRequest extends MockHttpServletRequest {
        private byte[] myBody;

        @Override
        public void setBodyContent(byte[] body) {
            myBody = body;
        }

        @Override
        public void setBodyContent(String body) {
            myBody = body.getBytes();
        }

        public ServletInputStream getInputStream() {
            return new GeoServerMockServletInputStream(myBody);
        }
    }

    private static class GeoServerMockServletInputStream extends ServletInputStream {
        private byte[] myBody;
        private int myOffset = 0;
        private int myMark = -1;

        public GeoServerMockServletInputStream(byte[] body) {
            myBody = body;
        }

        public int available() {
            return myBody.length - myOffset;
        }

        public void close() {
        }

        public void mark(int readLimit) {
            myMark = myOffset;
        }

        public void reset() {
            if (myMark < 0 || myMark >= myBody.length) {
                throw new IllegalStateException("Can't reset when no mark was set.");
            }

            myOffset = myMark;
        }

        public boolean markSupported() {
            return true;
        }

        public int read() {
            byte[] b = new byte[1];
            return read(b, 0, 1) == -1 ? -1 : b[0];
        }

        public int read(byte[] b) {
            return read(b, 0, b.length);
        }

        public int read(byte[] b, int offset, int length) {
            int realOffset = offset + myOffset;
            int i;

            if (realOffset >= myBody.length) {
                return -1;
            }
            for (i = 0; (i < length) && (i + myOffset < myBody.length); i++) {
                b[offset + i] = myBody[myOffset + i];
            }

            myOffset += i;

            return i;
        }

        public int readLine(byte[] b, int offset, int length) {
            int realOffset = offset + myOffset;
            int i;

            for (i = 0; (i < length) && (i + myOffset < myBody.length); i++) {
                b[offset + i] = myBody[myOffset + i];
                if (myBody[myOffset + i] == '\n')
                    break;
            }

            myOffset += i;

            return i;
        }
    }
}