freemarker.test.servlet.WebAppTestCase.java Source code

Java tutorial

Introduction

Here is the source code for freemarker.test.servlet.WebAppTestCase.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package freemarker.test.servlet;

import static org.junit.Assert.*;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.webapp.WebAppContext;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

public class WebAppTestCase {

    public static final String IGNORED_MASK = "[IGNORED]";

    private static final Logger LOG = LoggerFactory.getLogger(WebAppTestCase.class);

    private static final String ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN = "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern";

    private static final String EXPECTED_DIR = "/WEB-INF/expected/";

    private static Server server;
    private static ContextHandlerCollection contextHandlers;
    private static Map<String, WebAppContext> deployedWebApps = new HashMap<String, WebAppContext>();

    @BeforeClass
    public static void beforeClass() throws Exception {
        // Work around Java 5 bug(?) that causes Jasper to fail with "zip file closed" when it reads the JSTL jar:
        org.eclipse.jetty.util.resource.Resource.setDefaultUseCaches(false);

        LOG.info("Starting embedded Jetty...");

        server = new Server(0);

        contextHandlers = new ContextHandlerCollection();
        server.setHandler(contextHandlers);

        server.start();
    }

    @AfterClass
    public static void afterClass() throws Exception {
        LOG.info("Stopping embedded Jetty...");
        server.stop();
        server.join(); // TODO redundant?
    }

    protected final String getResponseContent(String webAppName, String webAppRelURL) throws Exception {
        HTTPResponse resp = getHTTPResponse(webAppName, webAppRelURL);
        if (resp.getStatusCode() != HttpURLConnection.HTTP_OK) {
            fail("Expected HTTP status " + HttpURLConnection.HTTP_OK + ", but got " + resp.getStatusCode()
                    + " (message: " + resp.getStatusMessage() + ") for URI " + resp.getURI());
        }
        return resp.getContent();
    }

    protected final int getResponseStatusCode(String webAppName, String webAppRelURL) throws Exception {
        HTTPResponse resp = getHTTPResponse(webAppName, webAppRelURL);
        return resp.getStatusCode();
    }

    protected final HTTPResponse getHTTPResponse(String webAppName, String webAppRelURL) throws Exception {
        if (webAppName.startsWith("/") || webAppName.endsWith("/")) {
            throw new IllegalArgumentException("\"webAppName\" can't start or end with \"/\": " + webAppName);
        }
        if (webAppRelURL.startsWith("/") || webAppRelURL.endsWith("/")) {
            throw new IllegalArgumentException("\"webappRelURL\" can't start or end with \"/\": " + webAppRelURL);
        }

        ensureWebAppIsDeployed(webAppName);

        final URI uri = new URI("http://localhost:" + server.getConnectors()[0].getLocalPort() + "/" + webAppName
                + "/" + webAppRelURL);

        final HttpURLConnection httpCon = (HttpURLConnection) uri.toURL().openConnection();
        httpCon.connect();
        try {
            LOG.debug("HTTP GET: {}", uri);

            final int responseCode = httpCon.getResponseCode();

            final String content;
            if (responseCode == 200) {
                InputStream in = httpCon.getInputStream();
                try {
                    content = IOUtils.toString(in, "UTF-8");
                } finally {
                    in.close();
                }
            } else {
                content = null;
            }

            return new HTTPResponse(responseCode, httpCon.getResponseMessage(), content, uri);
        } finally {
            httpCon.disconnect();
        }
    }

    /**
     * Compares the output of the JSP and the FTL version of the same page, ignoring some of the whitespace differences.
     * @param webAppRelURLWithoutExt something like {@code "tester?view=foo"}, which will be extended to
     *          {@code "tester?view=foo.jsp"} and {@code "tester?view=foo.ftl"}, and then the output of these extended
     *          URL-s will be compared.
     */
    protected void assertJSPAndFTLOutputEquals(String webAppName, String webAppRelURLWithoutExt) throws Exception {
        assertOutputsEqual(webAppName, webAppRelURLWithoutExt + ".jsp", webAppRelURLWithoutExt + ".ftl");
    }

    protected void assertOutputsEqual(String webAppName, String webAppRelURL1, final String webAppRelURL2)
            throws Exception {
        String jspOutput = normalizeWS(getResponseContent(webAppName, webAppRelURL1), true);
        String ftlOutput = normalizeWS(getResponseContent(webAppName, webAppRelURL2), true);
        assertEquals(jspOutput, ftlOutput);
    }

    protected void assertExpectedEqualsOutput(String webAppName, String expectedFileName, String webAppRelURL)
            throws Exception {
        assertExpectedEqualsOutput(webAppName, expectedFileName, webAppRelURL, true);
    }

    protected void assertExpectedEqualsOutput(String webAppName, String expectedFileName, String webAppRelURL,
            boolean compressWS) throws Exception {
        assertExpectedEqualsOutput(webAppName, expectedFileName, webAppRelURL, compressWS, null);
    }

    /**
     * @param expectedFileName
     *            The name of the file that stores the expected content, relatively to
     *            {@code servketContext:/WEB-INF/expected}.
     * @param ignoredParts
     *            Parts that will be search-and-replaced with {@value #IGNORED_MASK} with both in the expected and
     *            actual outputs.
     */
    protected void assertExpectedEqualsOutput(String webAppName, String expectedFileName, String webAppRelURL,
            boolean compressWS, List<Pattern> ignoredParts) throws Exception {
        final String actual = normalizeWS(getResponseContent(webAppName, webAppRelURL), compressWS);
        final String expected;
        {
            final InputStream in = new URL(getWebAppDirURL(webAppName) + EXPECTED_DIR + expectedFileName)
                    .openStream();
            try {
                expected = normalizeWS(IOUtils.toString(in, "utf-8"), compressWS);
            } finally {
                in.close();
            }
        }
        assertEquals(maskIgnored(expected, ignoredParts), maskIgnored(actual, ignoredParts));
    }

    private String maskIgnored(String s, List<Pattern> ignoredParts) {
        if (ignoredParts == null)
            return s;

        for (Pattern ignoredPart : ignoredParts) {
            s = ignoredPart.matcher(s).replaceAll(IGNORED_MASK);
        }
        return s;
    }

    protected synchronized void restartWebAppIfStarted(String webAppName) throws Exception {
        WebAppContext context = deployedWebApps.get(webAppName);
        if (context != null) {
            context.stop();
            context.start();
        }
    }

    private Pattern BR = Pattern.compile("\r\n|\r");
    private Pattern MULTI_LINE_WS = Pattern.compile("[\t ]*[\r\n][\t \r\n]*", Pattern.DOTALL);
    private Pattern SAME_LINE_WS = Pattern.compile("[\t ]+", Pattern.DOTALL);

    private String normalizeWS(String s, boolean compressWS) {
        if (compressWS) {
            return SAME_LINE_WS.matcher(MULTI_LINE_WS.matcher(s).replaceAll("\n")).replaceAll(" ").trim();
        } else {
            return BR.matcher(s).replaceAll("\n");
        }
    }

    private synchronized void ensureWebAppIsDeployed(String webAppName) throws Exception {
        if (deployedWebApps.containsKey(webAppName)) {
            return;
        }

        final String webAppDirURL = getWebAppDirURL(webAppName);

        WebAppContext context = new WebAppContext(webAppDirURL, "/" + webAppName);

        // Pattern of jar file names scanned for META-INF/*.tld:
        context.setAttribute(ATTR_JETTY_CONTAINER_INCLUDE_JAR_PATTERN, ".*taglib.*\\.jar$");

        contextHandlers.addHandler(context);
        // As we add this after the Server was started, it has to be started manually:
        context.start();

        deployedWebApps.put(webAppName, context);
        LOG.info("Deployed web app.: {}", webAppName);
    }

    @SuppressFBWarnings(value = "UI_INHERITANCE_UNSAFE_GETRESOURCE", justification = "By design relative to subclass")
    private String getWebAppDirURL(String webAppName) throws IOException {
        final URL webXmlURL;
        {
            final String relResPath = "webapps/" + webAppName + "/WEB-INF/web.xml";

            Class<?> baseClass = this.getClass();
            findWebXmlURL: do {
                URL r = baseClass.getResource(relResPath);
                if (r != null) {
                    webXmlURL = r;
                    break findWebXmlURL;
                }

                baseClass = baseClass.getSuperclass();
                if (!WebAppTestCase.class.isAssignableFrom(baseClass)) {
                    throw new IOException("Can't find test class relative resource: " + relResPath);
                }
            } while (true);
        }

        try {
            return webXmlURL.toURI().resolve("..").toString();
        } catch (URISyntaxException e) {
            throw new RuntimeException("Failed to get grandparent URL for " + webXmlURL, e);
        }
    }

    private static class HTTPResponse {
        private final int statusCode;
        private final String content;
        private final String statusMessage;
        private final URI uri;

        public HTTPResponse(int statusCode, String statusMessage, String content, URI uri) {
            this.statusCode = statusCode;
            this.content = content;
            this.statusMessage = statusMessage;
            this.uri = uri;
        }

        public String getStatusMessage() {
            return statusMessage;
        }

        public int getStatusCode() {
            return statusCode;
        }

        public String getContent() {
            return content;
        }

        public URI getURI() {
            return uri;
        }

    }

}