/* (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 static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; 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.CascadeDeleteVisitor; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.NamespaceInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.StoreInfo; import org.geoserver.catalog.StyleInfo; import org.geoserver.catalog.TestHttpClientProvider; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServerDataDirectory; import org.geoserver.config.GeoServerLoaderProxy; import org.geoserver.config.ServiceInfo; import org.geoserver.data.test.SystemTestData; import org.geoserver.logging.LoggingUtils; import org.geoserver.ows.util.CaseInsensitiveMap; 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.platform.ServiceException; import org.geoserver.security.AccessMode; import org.geoserver.security.GeoServerRoleService; import org.geoserver.security.GeoServerRoleStore; import org.geoserver.security.GeoServerSecurityManager; import org.geoserver.security.GeoServerUserGroupService; import org.geoserver.security.GeoServerUserGroupStore; import org.geoserver.security.impl.DataAccessRule; import org.geoserver.security.impl.DataAccessRuleDAO; import org.geoserver.security.impl.GeoServerRole; import org.geoserver.security.impl.GeoServerUser; import org.geoserver.security.impl.GeoServerUserGroup; import org.geoserver.security.password.GeoServerDigestPasswordEncoder; import org.geoserver.security.password.GeoServerPBEPasswordEncoder; import org.geoserver.security.password.GeoServerPlainTextPasswordEncoder; import org.geotools.data.DataUtilities; import org.geotools.data.simple.SimpleFeatureSource; import org.geotools.util.logging.Log4JLoggerFactory; import org.geotools.util.logging.Logging; import org.geotools.xml.XSD; import org.junit.After; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; 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.MockFilterChain; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletConfig; import org.springframework.mock.web.MockServletContext; import net.sf.json.JSON; import net.sf.json.JSONSerializer; /** * Base test class for GeoServer system tests that require a fully configured spring context and * work off a real data directory provided by {@link SystemTestData}. * <h2>Subclass Hooks</h2> * <p> * Subclasses extending this base class have the following hooks avaialble: * <ul> * <li>{@link #setUpTestData(SystemTestData)} - Perform post configuration of the {@link SystemTestData} * instance</li> * <li>{@link #onSetUp(SystemTestData)} - Perform setup after the system has been fully initialized * <li>{@link #onTearDown(SystemTestData)} - Perform teardown before the system is to be shutdown * </ul> * </p> * <h2>Test Setup Frequency</h2> * <p> * By default the setup cycle is executed once for extensions of this class. Subclasses that require * a different test setup frequency should annotate themselves with the appropriate {@link TestSetup} * annotation. For example to implement a repeated setup: * <code><pre> * {@literal @}TestSetup(run=TestSetupFrequency.REPEAT) * public class MyTest extends GeoServerSystemTestSupport { * * } * </pre></code> * </p> * @author Justin Deoliveira, OpenGeo * */ @TestSetup(run=TestSetupFrequency.ONCE) public class GeoServerSystemTestSupport extends GeoServerBaseTestSupport<SystemTestData> { protected SystemTestData createTestData() throws Exception { return new SystemTestData(); } /** * spring application context containing the integrated geoserver */ protected static GeoServerTestApplicationContext applicationContext; /** * credentials for mock requests */ protected String username, password; /** * Cached dispatcher, it has its own app context, so it's expensive to build */ protected static DispatcherServlet dispatcher; protected final void setUp(SystemTestData testData) throws Exception { // speed up xpath evaluations try { // see // http://stackoverflow.com/questions/6340802/java-xpath-apache-jaxp-implementation-performance Class.forName("com.sun.org.apache.xml.internal.dtm.ref.DTMManagerDefault"); System.setProperty("com.sun.org.apache.xml.internal.dtm.DTMManager", "com.sun.org.apache.xml.internal.dtm.ref.DTMManagerDefault"); } catch (Exception e) { // ignore on VM where this optimization does not apply } // disable security manager to speed up tests, we are spending a lot of time in privileged // blocks System.setSecurityManager(null); // 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); setUpTestData(testData); // put the mock http server in test mode TestHttpClientProvider.startTest(); // if we have data, create a mock servlet context and start up the spring configuration if (testData.isTestDataAvailable()) { //set up a fake WEB-INF directory 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); // we are on servlet 2.4 servletContext.setMinorVersion(4); servletContext.setInitParameter("GEOSERVER_DATA_DIR", testData.getDataDirectoryRoot() .getPath()); servletContext.setInitParameter("serviceStrategy", "PARTIAL-BUFFER2"); List<String> contexts = new ArrayList(); setUpSpring(contexts); applicationContext = new GeoServerTestApplicationContext( contexts.toArray(new String[contexts.size()]), servletContext); applicationContext.setUseLegacyGeoServerLoader(false); 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); dispatcher = buildDispatcher(); onSetUp(testData); } } protected final void tearDown(SystemTestData testData) throws Exception { if(testData.isTestDataAvailable()) { onTearDown(testData); destroyGeoServer(); TestHttpClientProvider.endTest(); // some tests do need a kick on the GC to fully clean up if(isMemoryCleanRequired()) { System.gc(); System.runFinalization(); } } } protected void destroyGeoServer() { if (applicationContext == null) { return; } getGeoServer().dispose(); 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); } finally { applicationContext = null; } } @After public void doLogout() { logout(); } // // subclass hooks // /** * Sets up the {@link SystemTestData} used for this test. * <p> * This method is used to add any additional data or configuration to the test setup and may * be overridden or extended. The default implementation calls * {@link SystemTestData#setUpDefaultLayers()} to add the default layers for the test. * </p> */ protected void setUpTestData(SystemTestData testData) throws Exception { testData.setUpDefault(); } /** * Subclass hook called after the system (ie spring context) has been fully initialized. * <p> * Subclasses should override for post setup that is needed. The default implementation does * nothing. * </p> */ protected void onSetUp(SystemTestData testData) throws Exception { } /** * Subclass hook called before the system (ie spring context) is to be shut down. * <p> * Subclasses should override for any cleanup / teardown that should occur on system shutdown. * </p> */ protected void onTearDown(SystemTestData testData) throws Exception { } /** * Sets up the spring context locations to use in constructing the spring context for this * system test. * <p> * Subclasses may override to provide additional context files/locations. * </p> */ protected void setUpSpring(List<String> springContextLocations) { springContextLocations.add("classpath*:/applicationContext.xml"); springContextLocations.add("classpath*:/applicationSecurityContext.xml"); } // // test behaviour methods // /** * 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() { if (isQuietTests()) { return "/QUIET_LOGGING.properties"; } return "/TEST_LOGGING.properties"; } /** * 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; } // // singleton access methods // /** * 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"); } /** * Accessor for plain text password encoder. */ protected GeoServerPlainTextPasswordEncoder getPlainTextPasswordEncoder() { return getSecurityManager().loadPasswordEncoder(GeoServerPlainTextPasswordEncoder.class); } /** * Accessor for digest password encoder. */ protected GeoServerDigestPasswordEncoder getDigestPasswordEncoder() { return getSecurityManager().loadPasswordEncoder(GeoServerDigestPasswordEncoder.class); } /** * Accessor for regular (weak encryption) pbe password encoder. */ protected GeoServerPBEPasswordEncoder getPBEPasswordEncoder() { return getSecurityManager().loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, null, false); } /** * Accessor for strong encryption pbe password encoder. */ protected GeoServerPBEPasswordEncoder getStrongPBEPasswordEncoder() { return getSecurityManager().loadPasswordEncoder(GeoServerPBEPasswordEncoder.class, null, true); } /** * Accessor for global resource loader instance from the test application context. */ protected GeoServerResourceLoader getResourceLoader() { return (GeoServerResourceLoader) applicationContext.getBean( "resourceLoader" ); } /** * 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; } } /** * Flush XSD if exists. */ protected static void disposeIfExists(XSD xsd) { if (xsd != null) { xsd.dispose(); } } // // lookup/accessor helper methods // /** * Asserts the content type taking into account that Spring-test insists on adding * the charset encoding to the content type (see https://jira.spring.io/browse/SPR-1717) * @param string * @param response */ protected static void assertContentType(String contentType, MockHttpServletResponse response) { String actual = response.getHeader("Content-Type"); assertNotNull(actual); assertThat(actual, startsWith(contentType)); } 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() ); } /** * 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)); } /** * 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) { return toString(layerName); } // // catalog state helpers // /** * Recursively deletes a workspace and everything under it. * <p> * If the workspace does not exist, this method will do nothing rather than fail. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param name Name of the workspace. */ protected void removeWorkspace(String name) { Catalog cat = getCatalog(); WorkspaceInfo ws = cat.getWorkspaceByName(name); if (ws != null) { new CascadeDeleteVisitor(cat).visit(ws); } } /** * Deletes a namespace. * <p> * If the namespace does not exist, this method will do nothing rather than fail. * </p> * <p> * To do a recursive delete of all the resources under the namespace the * {@link #removeWorkspace(String)} method shoul be used. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param prefix The prefix of the namespace. */ protected void removeNamespace(String prefix) { Catalog cat = getCatalog(); NamespaceInfo ns = cat.getNamespaceByPrefix(prefix); if (ns != null) { cat.remove(ns); } } /** * Recursively deletes a store and everything under it. * <p> * If the store does not exist, this method will do nothing rather than fail. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspaceName Name of the workspace of the store. * @param name Name of the store. */ protected void removeStore(String workspaceName, String name) { Catalog cat = getCatalog(); StoreInfo store = cat.getStoreByName(workspaceName, name, StoreInfo.class); if (store == null) { return; } CascadeDeleteVisitor v = new CascadeDeleteVisitor(getCatalog()); store.accept(v); } /** * Recursively deletes a layer and every resource associated with it. * <p> * If the layer does not exist, this method will do nothing rather than fail. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspaceName Name of the workspace/namespace of the layer. * @param name Name of the layer. */ protected void removeLayer(String workspaceName, String name) { Catalog cat = getCatalog(); ResourceInfo resource = cat.getResourceByName(workspaceName, name, ResourceInfo.class); if (resource == null) { return; } CascadeDeleteVisitor v = new CascadeDeleteVisitor(getCatalog()); for (LayerInfo layer : cat.getLayers()) { if(resource.equals(layer.getResource())) { layer.accept(v); } } } /** * Recursively deletes a style. * <p> * If the style does not exist, this method will do nothing rather than fail. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspaceName The optional workspace of the style, may be <code>null</code>. * @param name Name of the style. */ protected void removeStyle(String workspaceName, String name) throws IOException { Catalog cat = getCatalog(); StyleInfo s = workspaceName != null ? cat.getStyleByName(workspaceName, name) : cat.getStyleByName(name); if (s != null) { cat.remove(s); //remove the sld file as well cat.getResourcePool().deleteStyle(s, true); } else { //handle case of sld fiel still lying around File sld = workspaceName != null ? cat.getResourceLoader().find("workspaces", workspaceName, "styles", name + ".sld") : cat.getResourceLoader().find("styles", name + ".sld"); if (sld != null) { sld.delete(); } } } /** * Deletes a layer group. * <p> * If the layer group does not exist, this method will do nothing rather than fail. * </p> * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspaceName The optional workspace of the layer group, may be <code>null</code>. * @param name Name of the layer group. */ protected void removeLayerGroup(String workspaceName, String name) { Catalog cat = getCatalog(); LayerGroupInfo lg = workspaceName != null ? cat.getLayerGroupByName(workspaceName, name) : cat.getLayerGroupByName(name); if (lg != null) { cat.remove(lg); } } /** * Reverts a layer back to its original state, both data and configuration. * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspace The name of the workspace/namespace containing the layer. * @param name The name of the layer. * */ protected void revertLayer(String workspace, String name) throws IOException { revertLayer(new QName(workspace, name)); } /** * Reverts a layer back to its original state, both data and configuration. * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param qName The qualified name of the layer. * */ protected void revertLayer(QName qName) throws IOException { getTestData().addVectorLayer(qName, getCatalog()); } /** * Reverts a service back to its original configuration state. * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param serviceClass The class/interface of the service object. * @param workspace The optional workspace containing the service config, may be <code>null</code>. * */ protected void revertService(Class<? extends ServiceInfo> serviceClass, String workspace) { getTestData().addService(serviceClass, workspace, getGeoServer()); } /** * Reverts settings back to original configuration state. * <p> * This method is intended to be called after system startup. Typically from * {@link #onSetUp(SystemTestData)} or a {@literal @}Before hook. * </p> * @param workspace The optional workspace containing the settings config, may be <code>null</code>. * */ protected void revertSettings(String workspace) { getTestData().addSettings(workspace, getGeoServer()); } // // authentication/security helpers // /** * Sets the authentication for this test run (will be removed during {@link #tearDown()} * ). 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 setRequestAuth(String username, String password) { this.username = username; this.password = password; } /** * Sets up the authentication context for the test. * <p> * This context lasts only for a single test case, it is cleared after every test has completed. * </p> * @param username The username. * @param password The password. * @param roles Roles to assign. */ protected void login(String username, String password, String... roles) { SecurityContextHolder.setContext(new SecurityContextImpl()); List<GrantedAuthority> l= new ArrayList<GrantedAuthority>(); for (String role : roles) { l.add(new SimpleGrantedAuthority(role)); } SecurityContextHolder.getContext().setAuthentication( new UsernamePasswordAuthenticationToken(username,password,l)); } protected void addUser(String username, String password, List<String> groups, List<String> roles) throws Exception { GeoServerSecurityManager secMgr = getSecurityManager(); GeoServerUserGroupService ugService = secMgr.loadUserGroupService("default"); GeoServerUserGroupStore ugStore = ugService.createStore(); GeoServerUser user = ugStore.createUserObject(username, password, true); ugStore.addUser(user); if (groups != null && !groups.isEmpty()) { for (String groupName : groups) { GeoServerUserGroup group = ugStore.getGroupByGroupname(groupName); if (group == null) { group = ugStore.createGroupObject(groupName, true); ugStore.addGroup(group); } ugStore.associateUserToGroup(user, group); } } ugStore.store(); if (roles != null && !roles.isEmpty()) { GeoServerRoleService roleService = secMgr.getActiveRoleService(); GeoServerRoleStore roleStore = roleService.createStore(); for (String roleName : roles) { GeoServerRole role = roleStore.getRoleByName(roleName); if (role == null) { role = roleStore.createRoleObject(roleName); roleStore.addRole(role); } roleStore.associateRoleToUser(role, username); } roleStore.store(); } } protected void addLayerAccessRule(String workspace, String layer, AccessMode mode, String... roles) throws IOException { DataAccessRuleDAO dao = DataAccessRuleDAO.get(); DataAccessRule rule = new DataAccessRule(); rule.setRoot(workspace); rule.setLayer(layer); rule.setAccessMode(mode); rule.getRoles().addAll(Arrays.asList(roles)); dao.addRule(rule); dao.storeRules(); } /** * Clears the authentication context. * <p> * This method is called after each test case * </p> */ protected void logout() { SecurityContextHolder.clearContext(); } protected MockHttpServletRequest createRequest(String path) { return createRequest(path, false); } // // request/response helpers // /** * 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, boolean createSession) { MockHttpServletRequest request = new GeoServerMockHttpServletRequest(); request.setScheme("http"); request.setServerName("localhost"); request.setServerPort(8080); request.setContextPath("/geoserver"); request.setRequestURI(ResponseUtils.stripQueryString(ResponseUtils.appendPath( "/geoserver/", path))); // request.setRequestURL(ResponseUtils.appendPath("http://localhost:8080/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( ResponseUtils.stripQueryString(path)))); request.addHeader("Host", "localhost:8080"); // 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); if(createSession) { 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&..." * * @param responseCode Expected HTTP code, will provide exception if not matched * @return An input stream which is the result of the request. */ protected InputStream get( String path, int responseCode ) throws Exception { MockHttpServletResponse response = getAsServletResponse(path); int status = response.getStatus(); if( responseCode != status ){ String content = response.getContentAsString(); if( content == null || content.length() == 0 ){ throw new ServiceException("expected status <"+responseCode+"> but was <"+status+">"); } else { throw new ServiceException("expected status <"+responseCode+"> but was <"+status+">:"+content); } } 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); 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'. * @param xml The body content, often xml for OGC services * @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, often xml for OGC services * @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(getBinary(response)); } /** * 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 byte[] getBinary(MockHttpServletResponse response) { return 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()); } /** * 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, often xml for OGC services * @param contentType * @return the servlet response */ 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")); return dispatch(request); } protected MockHttpServletResponse postAsServletResponse(String path, String body, String contentType, String charset) throws Exception { MockHttpServletRequest request = createRequest(path); request.setMethod("POST"); request.setContentType(contentType); request.setContent(body.getBytes("UTF-8")); return dispatch(request, charset); } protected MockHttpServletResponse postAsServletResponse(String path, byte[] body, String contentType ) throws Exception { MockHttpServletRequest request = createRequest(path); request.setMethod("POST"); request.setContentType(contentType); request.setContent(body); 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&..." * * @return A result of the request parsed into a dom. * */ protected Document getAsDOM(final String path) throws Exception { return getAsDOM(path, true); } /** * 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 statusCode Expected status code * * @return A result of the request parsed into a dom. */ protected Document getAsDOM(final String path, int statusCode) throws Exception { InputStream responseContent = get(path,statusCode); return dom(responseContent, true); } /** * Executes an ows request using the GET method and returns the result as an * xml document, with the ability to override the XML document encoding. * * @param path The portion of the request after the context, * example: 'wms?request=GetMap&version=1.1.1&..." * @param encoding Override for the encoding of the document. * * @return A result of the request parsed into a dom. * */ protected Document getAsDOM(final String path, String encoding) throws Exception { return getAsDOM(path, true, encoding); } /** * Executes an ows request using the GET method and returns the result as an * JSON document. * * @param path The portion of the request after the context, * example: 'wms?request=GetMap&version=1.1.1&..." * @param statusCode Expected status code * * @return A result of the request parsed into a dom. */ protected JSON getAsJSON(final String path, int statusCode) throws Exception { MockHttpServletResponse response = getAsServletResponse(path, statusCode); int status = response.getStatus(); if( statusCode != status ){ String content = response.getContentAsString(); if( content == null || content.length() == 0 ){ throw new ServiceException("expected status <"+statusCode+"> but was <"+status+">"); } else { throw new ServiceException("expected status <"+statusCode+"> but was <"+status+">:"+content); } } return json(response); } private MockHttpServletResponse getAsServletResponse(String path, int statusCode) throws Exception { MockHttpServletResponse response = getAsServletResponse(path); int status = response.getStatus(); if( statusCode != status ){ String content = response.getContentAsString(); if( content == null || content.length() == 0 ){ throw new ServiceException("expected status <"+statusCode+"> but was <"+status+">"); } else { throw new ServiceException("expected status <"+statusCode+"> but was <"+status+">:"+content); } } return response; } /** * 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); } /** * Retries the request result as a BufferedImage, checking the mime type is the expected one * @param path * @param mime * */ protected BufferedImage getAsImage(String path, String mime) throws Exception { MockHttpServletResponse resp = getAsServletResponse(path); assertEquals(mime, resp.getContentType()); InputStream is = getBinaryInputStream(resp); return ImageIO.read(is); } /** * Retrieves the request result as a list of BufferedImages from an animated format (works with GIF, * other formats are not tested so far). */ protected List<BufferedImage> getAsAnimation(String path, String mime) throws Exception { MockHttpServletResponse resp = getAsServletResponse(path); assertEquals(mime, resp.getContentType()); try (ImageInputStream is = ImageIO.createImageInputStream(getBinaryInputStream(resp))) { ImageReader reader = ImageIO.getImageReaders(is).next(); reader.setInput(is); final int numImages = reader.getNumImages(true); List<BufferedImage> result = new ArrayList<>(numImages); for (int i = 0; i < numImages; i++) { result.add(reader.read(i)); } return result; } } /** * 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 { InputStream responseContent = get(path); return dom(responseContent, skipDTD); } /** * 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 * * @param encoding * Overide for the encoding of the document. * * @return A result of the request parsed into a dom. * */ protected Document getAsDOM(final String path, final boolean skipDTD, String encoding) throws Exception { return dom(get(path), skipDTD, encoding); } /** * 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 ) 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 { return dom(input, skipDTD, null); } protected Document dom(InputStream stream, boolean skipDTD, String encoding) throws ParserConfigurationException, SAXException, IOException { InputSource input = new InputSource(stream); if (encoding != null) { input.setEncoding(encoding); } else { input.setEncoding(Charset.defaultCharset().name()); } 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); } } 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) { charset = Charset.defaultCharset().name(); } response = new MockHttpServletResponse(); response.setCharacterEncoding(charset); dispatch(request, response); return response; } protected DispatcherServlet getDispatcher() throws Exception { return dispatcher; } protected DispatcherServlet buildDispatcher() throws ServletException { // create an instance of the spring dispatcher ServletContext context = applicationContext.getServletContext(); MockServletConfig config = new MockServletConfig(context, "dispatcher"); DispatcherServlet dispatcher = new DispatcherServlet(applicationContext); 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); } /* * 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; request.addParameter(key, values); } } } /** * 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()); } // // xml validation helpers // /** * 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 * @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 { checkOws11Exception(dom, exceptionCode, 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, String locator) 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") ); } if( locator != null) { assertEquals( 1, dom.getElementsByTagName( "ows:Exception").getLength() ); Element ex = (Element) dom.getElementsByTagName( "ows:Exception").item(0); assertEquals( locator, ex.getAttribute( "locator") ); } } /** * Performs basic checks on an OWS 2.0 exception. The check for status, exception code and locator * is optional, leave null if you don't want to check it. * @returns Returns the message of the inner exception. */ protected String checkOws20Exception(MockHttpServletResponse response, Integer status, String exceptionCode, String locator) throws Exception { // check the http level assertEquals("application/xml", response.getContentType()); if (status != null) { assertEquals(status.intValue(), response.getStatus()); } // check the returned xml Document dom = dom(new ByteArrayInputStream(response.getContentAsString().getBytes())); Element root = dom.getDocumentElement(); assertEquals("ows:ExceptionReport", root.getNodeName()); assertEquals("2.0.0", root.getAttribute("version")); assertEquals("http://www.opengis.net/ows/2.0", root.getAttribute("xmlns:ows")); // look into exception code and locator 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")); } assertEquals(1, dom.getElementsByTagName("ows:ExceptionText").getLength()); return dom.getElementsByTagName("ows:ExceptionText").item(0).getTextContent(); } // // misc utilities // /** * 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(); } /** * 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; } /** * 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 { if (isQuietTests()) { return; } print (dom, System.out); } /** * Pretty-print a {@link Document} to an {@link OutputStream}. * * @param document * document to be prettified * @param output * stream to which output is written */ protected void print(Document document, OutputStream output) { try { Transformer tx = TransformerFactory.newInstance().newTransformer(); tx.setOutputProperty(OutputKeys.INDENT, "yes"); tx.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); tx.transform(new DOMSource(document), new StreamResult(output)); } catch (Exception e) { throw new RuntimeException(e); } } /** * Utility method to print out the contents of an input stream. */ protected void print( InputStream in ) throws Exception { if (isQuietTests()) { return; } 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 ) { if (isQuietTests()) { return; } 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 ); } /** * 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; } /** * Parses a raw set of kvp's into a parsed set of kvps. * * @param raw Map of String,String. */ protected Map parseKvp(Map /*<String,String>*/ raw) throws Exception { // parse like the dispatcher but make sure we don't change the original map HashMap input = new HashMap(raw); List<Throwable> errors = KvpUtils.parse(input); if(errors != null && errors.size() > 0) throw (Exception) errors.get(0); return caseInsensitiveKvp(input); } protected Map caseInsensitiveKvp(Map input) { // make it case insensitive like the servlet+dispatcher maps Map result = new HashMap(); for (Iterator it = input.keySet().iterator(); it.hasNext();) { String key = (String) it.next(); result.put(key.toUpperCase(), input.get(key)); } return new CaseInsensitiveMap(result); } /** * Checks the image and its sources are all deferred loaded, that is, there is no BufferedImage in the chain * @param image */ protected void assertDeferredLoading(RenderedImage image) { if(image instanceof BufferedImage) { fail("Found a buffered image in the chain, the original image is not fully deferred loaded"); } else { for (RenderedImage ri : image.getSources()) { assertDeferredLoading(ri); } } } public static class GeoServerMockHttpServletRequest extends MockHttpServletRequest { private byte[] myBody; @Override public void setContent(byte[] body) { myBody = body; } @Override public BufferedReader getReader() { if (null == myBody) return null; return new BufferedReader(new StringReader(new String(myBody))); } 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 (myBody==null ||myMark < 0 || myMark >= myBody.length){ if(myBody==null || myBody.length==0) { //This prevents an annoying error when the sting is empty or null return; } 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 (myBody==null || 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; } } }