/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* 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.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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 org.apache.commons.codec.binary.Base64;
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.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.GeoServerExtensionsHelper;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.security.GeoServerSecurityManager;
import org.geotools.data.DataUtilities;
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.core.io.DefaultResourceLoader;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
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 org.springframework.mock.web.MockHttpServletResponse;
import net.sf.json.JSON;
import net.sf.json.JSONSerializer;
/**
* 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;
private boolean validating = false;
String username;
String password;
/**
* Returns a test data instance
*
*
*/
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;
}
public boolean isValidating() {
return validating;
}
public void setValidating(boolean validating) {
this.validating = validating;
}
/**
* 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()) {
org.springframework.core.io.ResourceLoader rl;
if (testData.getDataDirectoryRoot().canWrite()) {
File webinf = new File(testData.getDataDirectoryRoot(), "WEB-INF");
webinf.mkdir();
rl = new DirectoryResourceLoader(testData.getDataDirectoryRoot());
} else {
rl = new DefaultResourceLoader();
}
MockServletContext servletContext = new MockServletContext(rl);
servletContext.setInitParameter("GEOSERVER_DATA_DIR", testData.getDataDirectoryRoot()
.getPath());
// we are on servlet 2.4
servletContext.setMinorVersion(4);
servletContext.setInitParameter("serviceStrategy", "PARTIAL-BUFFER2");
applicationContext = new GeoServerTestApplicationContext(getSpringContextLocations(),
servletContext);
applicationContext.setValidating(validating);
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.
*
*/
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".
*
*/
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
*
*/
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
*
*/
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
GeoServerExtensionsHelper.init(null);
// some tests do need a kick on the GC to fully clean up
if(isMemoryCleanRequired()) {
System.gc();
System.runFinalization();
}
if(getTestData() != null) {
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(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<javax.servlet.Filter> getFilters() {
* return Collections.singletonList((javax.servlet.Filter) GeoServerExtensions
* .bean("filterChainProxy"));
* }
* </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(), "security");
* security.mkdir();
*
* File users = new File(security, "users.properties");
* Properties props = new Properties();
* props.put("admin", "geoserver,ROLE_ADMINISTRATOR");
* props.store(new FileOutputStream(users), "");
* }
* </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
*
*/
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.
*
*/
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(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.
*
*/
protected InputStream get( String path ) throws Exception {
MockHttpServletResponse response = getAsServletResponse(path);
return new ByteArrayInputStream( response.getContentAsString().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
*
*/
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.setContent(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.
*
*/
protected InputStream post( String path ) throws Exception {
MockHttpServletRequest request = createRequest( path );
request.setMethod( "POST" );
request.setContentType( "application/x-www-form-urlencoded" );
request.setContent(new byte[]{});
MockHttpServletResponse response = dispatch( request );
return new ByteArrayInputStream( response.getContentAsString().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"
*
*/
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
*
*/
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
*
*/
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
*
*/
protected InputStream put(String path, byte[] body, String contentType) throws Exception {
MockHttpServletResponse response = putAsServletResponse(path, body, contentType);
return new ByteArrayInputStream(response.getContentAsString().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.setContent(body);
request.addHeader("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.
*
*/
protected InputStream post( String path , String xml ) throws Exception {
MockHttpServletResponse response = postAsServletResponse(path, xml);
return new ByteArrayInputStream(response.getContentAsString().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
*
*/
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
*
*/
protected ByteArrayInputStream getBinaryInputStream(MockHttpServletResponse response) {
return new ByteArrayInputStream(response.getContentAsByteArray());
}
/**
* 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.
*
*/
protected InputStream post(String path, String body, String contentType) throws Exception{
MockHttpServletResponse response = postAsServletResponse(path, body, contentType);
return new ByteArrayInputStream(response.getContentAsString().getBytes());
}
protected MockHttpServletResponse postAsServletResponse(String path, String body, String contentType) throws Exception {
MockHttpServletRequest request = createRequest(path);
request.setMethod("POST");
request.setContentType(contentType);
request.setContent(body.getBytes("UTF-8"));
request.addHeader("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.
*
*/
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) throws UnsupportedEncodingException {
String content = response.getContentAsString();
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.
*
*/
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.
*
*/
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.
*
*/
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.
*
*/
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.
*
*/
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.addParameter(key, (String) value);
} else {
String[] values = (String[]) value;
for (String v: values) {
request.addParameter(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(context, "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
HttpServlet servlet = 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);
}
}
};
List<Filter> filterList = getFilters();
MockFilterChain chain;
if(filterList != null) {
chain = new MockFilterChain(servlet, (Filter[]) filterList.toArray(new Filter[filterList.size()]));
} else {
chain = new MockFilterChain(servlet);
}
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
*
*/
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.setContent(body.getBytes("UTF-8"));
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;
public GeoServerMockHttpServletRequest() {
super();
}
public GeoServerMockHttpServletRequest(String method, String requestURI) {
super(method, requestURI);
}
@Override
public void setContent(byte[] body) {
myBody = body;
}
public ServletInputStream getInputStream(){
return new GeoServerDelegatingServletInputStream(myBody);
}
@Override
public String toString() {
return "GeoServerMockHttpServletRequest "+getMethod()+ " "+getRequestURI();
}
}
private static class GeoServerDelegatingServletInputStream extends ServletInputStream {
private byte[] myBody;
private int myOffset = 0;
private int myMark = -1;
public GeoServerDelegatingServletInputStream(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;
}
}
}