com.github.jrialland.ajpclient.AbstractTomcatTest.java Source code

Java tutorial

Introduction

Here is the source code for com.github.jrialland.ajpclient.AbstractTomcatTest.java

Source

/* Copyright (c) 2014 Julien Rialland <julien.rialland@gmail.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package com.github.jrialland.ajpclient;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.DatatypeConverter;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.junit.After;
import org.junit.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockServletConfig;
import org.springframework.mock.web.MockServletContext;

/**
 * To be extended by test cases that need to have a real tomcat running
 *
 * @author Julien Rialland <julien.rialland@gmail.com>
 *
 */
public abstract class AbstractTomcatTest {

    private final Logger logger = LoggerFactory.getLogger(getClass().getName());

    private Tomcat tomcat;

    private ThreadPoolExecutor executor;

    private Path tempDir;

    private final Map<String, Servlet> servlets = new TreeMap<String, Servlet>();

    protected enum Protocol {
        Ajp("AJP/1.3"), Http("HTTP/1.1");

        private String proto;

        Protocol(final String proto) {
            this.proto = proto;
        }

        public String getScheme() {
            return proto.replaceAll("/.+$", "://").toLowerCase();
        }

        public String getProto() {
            return proto;
        }
    }

    private final Protocol protocol;

    /**
     *
     * @param protocol
     *            wether to serve http or ajp
     */
    public AbstractTomcatTest(final Protocol protocol) {
        this.protocol = protocol;
        try {
            tempDir = Files.createTempDirectory("tomcat-test-");
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    public AbstractTomcatTest(final Protocol protocol, final int maxThreads) {
        this(protocol);
        executor = new ThreadPoolExecutor(0, maxThreads, 10, TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(maxThreads));
    }

    public Tomcat getTomcat() {
        return tomcat;
    }

    public Path getTempDir() {
        return tempDir;
    }

    @Before
    public void before() throws LifecycleException, IOException {

        tomcat = new Tomcat();
        tomcat.setBaseDir(tempDir.toString());
        tomcat.getHost().setAppBase(tempDir.toString());
        tomcat.getHost().setAutoDeploy(true);
        tomcat.getHost().setDeployOnStartup(true);

        final Connector connector = new Connector(protocol.getProto());

        if (executor != null) {
            connector.setAttribute("executor", executor);
        }
        connector.setAttribute("address", "localhost");
        connector.setPort(0);
        tomcat.setConnector(connector);
        tomcat.getService().addConnector(connector);

        tomcat.start();

        final Path root = Files.createDirectory(tempDir.resolve("ROOT"));
        final Context rootContext = tomcat.addContext("/", root.toString());
        for (final Entry<String, Servlet> entry : servlets.entrySet()) {
            Tomcat.addServlet(rootContext, entry.getValue().toString(), entry.getValue())
                    .addMapping(entry.getKey());
            logger.info(getUri() + entry.getKey() + " => " + entry.getValue());
        }
    }

    /**
     * binds a servlet on the tomcat instance. the 'mapping' has the same format
     * that the 'servlet-mapping' parameter in web.xml. the path of the servlet
     * will be relative to /
     *
     * {@link Servlet#init(javax.servlet.ServletConfig)} is called with a mock
     * object as parameter.
     *
     * @param mapping
     * @param servlet
     */
    protected void addServlet(final String mapping, final Servlet servlet) {
        final MockServletContext servletContext = new MockServletContext("/");
        final MockServletConfig config = new MockServletConfig(servletContext, servlet.toString());
        try {
            servlet.init(config);
        } catch (final Exception e) {
            throw new IllegalStateException(e);
        }
        servlets.put(mapping, servlet);
    }

    @SuppressWarnings("serial")
    protected void addStaticResource(final String mapping, final Path file) {

        if (!Files.isRegularFile(file)) {
            final FileNotFoundException fnf = new FileNotFoundException(file.toString());
            fnf.fillInStackTrace();
            throw new IllegalArgumentException(fnf);
        }

        String md5;
        try {
            final InputStream is = file.toUri().toURL().openStream();
            md5 = computeMd5(is);
            is.close();
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
        final String fMd5 = md5;

        addServlet(mapping, new HttpServlet() {
            @Override
            protected void service(final HttpServletRequest req, final HttpServletResponse resp)
                    throws ServletException, IOException {
                resp.setContentLength((int) Files.size(file));
                resp.setHeader("Content-MD5", fMd5);
                final String mime = Files.probeContentType(file);
                if (mime != null) {
                    resp.setContentType(mime);
                }
                final OutputStream out = resp.getOutputStream();
                Files.copy(file, out);
                out.flush();
            }
        });
    }

    protected static String computeMd5(final InputStream is) {
        try {
            final MessageDigest md5 = MessageDigest.getInstance("md5");
            final byte[] buf = new byte[1024];
            int c = 0;
            while ((c = is.read(buf)) > -1) {
                md5.update(buf, 0, c);
            }
            return DatatypeConverter.printBase64Binary(md5.digest());
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected int getPort() {
        return tomcat.getConnector().getLocalPort();
    }

    protected String getUri() {
        final InetAddress addr = (InetAddress) tomcat.getConnector().getAttribute("address");

        return protocol.getScheme() + addr.getHostAddress() + ":" + getPort();
    }

    @After
    public void after() throws IOException {
        try {
            tomcat.stop();
            tomcat.destroy();
        } catch (final Exception e) {
            logger.error("could not stop tomcat", e);
        }

        deleteDirectory(tempDir);
    }

    protected static void deleteDirectory(final Path path) throws IOException {
        Files.walkFileTree(path, new FileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs)
                    throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(final Path file, final IOException exc) throws IOException {
                throw exc;
            }
        });
    }
}