demo.tomcat.SciTomcatEmbeddedServletContainerFactory.java Source code

Java tutorial

Introduction

Here is the source code for demo.tomcat.SciTomcatEmbeddedServletContainerFactory.java

Source

package demo.tomcat;

/*
 * Copyright 2012-2015 the original author or authors.
 *
 * 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.
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;

import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ErrorPage;
import org.springframework.boot.context.embedded.MimeMappings;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.context.embedded.Ssl.ClientAuth;
import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedWebappClassLoader;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;

/**
 * {@link EmbeddedServletContainerFactory} that can be used to create
 * {@link TomcatEmbeddedServletContainer}s. Can be initialized using Spring's
 * {@link ServletContextInitializer}s or Tomcat {@link LifecycleListener}s.
 * <p>
 * Unless explicitly configured otherwise this factory will created containers that
 * listens for HTTP requests on port 8080.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Brock Mills
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @see #setPort(int)
 * @see #setContextLifecycleListeners(Collection)
 * @see TomcatEmbeddedServletContainer
 */
public class SciTomcatEmbeddedServletContainerFactory extends AbstractEmbeddedServletContainerFactory
        implements ResourceLoaderAware {

    private static final Set<Class<?>> SCIs = new HashSet<Class<?>>();

    public static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";

    private File baseDirectory;

    private List<Valve> contextValves = new ArrayList<Valve>();

    private List<LifecycleListener> contextLifecycleListeners = new ArrayList<LifecycleListener>();

    private List<TomcatContextCustomizer> tomcatContextCustomizers = new ArrayList<TomcatContextCustomizer>();

    private List<TomcatConnectorCustomizer> tomcatConnectorCustomizers = new ArrayList<TomcatConnectorCustomizer>();

    private List<Connector> additionalTomcatConnectors = new ArrayList<Connector>();

    private ResourceLoader resourceLoader;

    private String protocol = DEFAULT_PROTOCOL;

    private String tldSkip;

    private String uriEncoding = "UTF-8";

    /**
     * Create a new {@link TomcatEmbeddedServletContainerFactory} instance.
     */
    public SciTomcatEmbeddedServletContainerFactory() {
        super();
    }

    /**
     * Create a new {@link TomcatEmbeddedServletContainerFactory} that listens for
     * requests using the specified port.
     * @param port the port to listen on
     */
    public SciTomcatEmbeddedServletContainerFactory(int port) {
        super(port);
    }

    /**
     * Create a new {@link TomcatEmbeddedServletContainerFactory} with the specified
     * context path and port.
     * @param contextPath root the context path
     * @param port the port to listen on
     */
    public SciTomcatEmbeddedServletContainerFactory(String contextPath, int port) {
        super(contextPath, port);
    }

    @Override
    public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        File baseDir = (this.baseDirectory != null ? this.baseDirectory : createTempDir("tomcat"));
        tomcat.setBaseDir(baseDir.getAbsolutePath());
        Connector connector = new Connector(this.protocol);
        tomcat.getService().addConnector(connector);
        customizeConnector(connector);
        tomcat.setConnector(connector);
        tomcat.getHost().setAutoDeploy(false);
        tomcat.getEngine().setBackgroundProcessorDelay(-1);
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
        prepareContext(tomcat.getHost(), initializers);
        return getTomcatEmbeddedServletContainer(tomcat);
    }

    protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File docBase = getValidDocumentRoot();
        docBase = (docBase != null ? docBase : createTempDir("tomcat-docbase"));
        TomcatEmbeddedContext context = new TomcatEmbeddedContext();
        context.setName(getContextPath());
        context.setPath(getContextPath());
        context.setDocBase(docBase.getAbsolutePath());
        context.addLifecycleListener(new FixContextListener());
        context.setParentClassLoader(this.resourceLoader != null ? this.resourceLoader.getClassLoader()
                : ClassUtils.getDefaultClassLoader());
        SkipPatternJarScanner.apply(context, this.tldSkip);
        WebappLoader loader = new WebappLoader(context.getParentClassLoader());
        loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
        loader.setDelegate(true);
        context.setLoader(loader);
        if (isRegisterDefaultServlet()) {
            addDefaultServlet(context);
        }
        if (isRegisterJspServlet() && ClassUtils.isPresent(getJspServletClassName(), getClass().getClassLoader())) {
            addJspServlet(context);
            addJasperInitializer(context);
            context.addLifecycleListener(new StoreMergedWebXmlListener());
        }
        ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
        configureContext(context, initializersToUse);
        host.addChild(context);
        postProcessContext(context);
    }

    private void addDefaultServlet(Context context) {
        Wrapper defaultServlet = context.createWrapper();
        defaultServlet.setName("default");
        defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
        defaultServlet.addInitParameter("debug", "0");
        defaultServlet.addInitParameter("listings", "false");
        defaultServlet.setLoadOnStartup(1);
        // Otherwise the default location of a Spring DispatcherServlet cannot be set
        defaultServlet.setOverridable(true);
        context.addChild(defaultServlet);
        context.addServletMapping("/", "default");
    }

    private void addJspServlet(Context context) {
        Wrapper jspServlet = context.createWrapper();
        jspServlet.setName("jsp");
        jspServlet.setServletClass(getJspServletClassName());
        jspServlet.addInitParameter("fork", "false");
        jspServlet.setLoadOnStartup(3);
        context.addChild(jspServlet);
        context.addServletMapping("*.jsp", "jsp");
        context.addServletMapping("*.jspx", "jsp");
    }

    private void addJasperInitializer(TomcatEmbeddedContext context) {
        try {
            ServletContainerInitializer initializer = (ServletContainerInitializer) ClassUtils
                    .forName("org.apache.jasper.servlet.JasperInitializer", null).newInstance();
            context.addServletContainerInitializer(initializer, null);
        } catch (Exception ex) {
            // Probably not Tomcat 8
        }
    }

    // Needs to be protected so it can be used by subclasses
    protected void customizeConnector(Connector connector) {
        int port = (getPort() >= 0 ? getPort() : 0);
        connector.setPort(port);
        if (connector.getProtocolHandler() instanceof AbstractProtocol) {
            if (getAddress() != null) {
                ((AbstractProtocol<?>) connector.getProtocolHandler()).setAddress(getAddress());
            }
        }
        if (getUriEncoding() != null) {
            connector.setURIEncoding(getUriEncoding());
        }

        // If ApplicationContext is slow to start we want Tomcat not to bind to the socket
        // prematurely...
        connector.setProperty("bindOnInit", "false");

        if (getSsl() != null && getSsl().isEnabled()) {
            Assert.state(connector.getProtocolHandler() instanceof AbstractHttp11JsseProtocol,
                    "To use SSL, the connector's protocol handler must be an "
                            + "AbstractHttp11JsseProtocol subclass");
            configureSsl((AbstractHttp11JsseProtocol<?>) connector.getProtocolHandler(), getSsl());
            connector.setScheme("https");
            connector.setSecure(true);
        }

        for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
            customizer.customize(connector);
        }
    }

    /**
     * Configure Tomcat's {@link AbstractHttp11JsseProtocol} for SSL.
     * @param protocol the protocol
     * @param ssl the ssl details
     */
    protected void configureSsl(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
        protocol.setSSLEnabled(true);
        protocol.setSslProtocol(ssl.getProtocol());
        configureSslClientAuth(protocol, ssl);
        protocol.setKeystorePass(ssl.getKeyStorePassword());
        protocol.setKeyPass(ssl.getKeyPassword());
        protocol.setKeyAlias(ssl.getKeyAlias());
        configureSslKeyStore(protocol, ssl);
        String ciphers = StringUtils.arrayToCommaDelimitedString(ssl.getCiphers());
        protocol.setCiphers(ciphers);
        configureSslTrustStore(protocol, ssl);
    }

    private void configureSslClientAuth(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
        if (ssl.getClientAuth() == ClientAuth.NEED) {
            protocol.setClientAuth(Boolean.TRUE.toString());
        } else if (ssl.getClientAuth() == ClientAuth.WANT) {
            protocol.setClientAuth("want");
        }
    }

    private void configureSslKeyStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
        try {
            File file = ResourceUtils.getFile(ssl.getKeyStore());
            protocol.setKeystoreFile(file.getAbsolutePath());
        } catch (FileNotFoundException ex) {
            throw new EmbeddedServletContainerException("Could not find key store " + ssl.getKeyStore(), ex);
        }
        if (ssl.getKeyStoreType() != null) {
            protocol.setKeystoreType(ssl.getKeyStoreType());
        }
        if (ssl.getKeyStoreProvider() != null) {
            protocol.setKeystoreProvider(ssl.getKeyStoreProvider());
        }
    }

    private void configureSslTrustStore(AbstractHttp11JsseProtocol<?> protocol, Ssl ssl) {
        if (ssl.getTrustStore() != null) {
            try {
                File file = ResourceUtils.getFile(ssl.getTrustStore());
                protocol.setTruststoreFile(file.getAbsolutePath());
            } catch (FileNotFoundException ex) {
                throw new EmbeddedServletContainerException("Could not find trust store " + ssl.getTrustStore(),
                        ex);
            }
        }
        protocol.setTruststorePass(ssl.getTrustStorePassword());
        if (ssl.getTrustStoreType() != null) {
            protocol.setTruststoreType(ssl.getTrustStoreType());
        }
        if (ssl.getTrustStoreProvider() != null) {
            protocol.setTruststoreProvider(ssl.getTrustStoreProvider());
        }
    }

    protected void addServletContainerInitializer(Class<?> clazz) {
        if (clazz != null) {
            SCIs.add(clazz);
        }
    }

    /**
     * Configure the Tomcat {@link Context}.
     * @param context the Tomcat context
     * @param initializers initializers to apply
     */
    protected void configureContext(Context context, ServletContextInitializer[] initializers) {
        TomcatStarter starter = new TomcatStarter(initializers);
        if (context instanceof TomcatEmbeddedContext) {
            // Should be true
            ((TomcatEmbeddedContext) context).setStarter(starter);
        }
        context.addServletContainerInitializer(starter, SCIs);

        for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
            context.addLifecycleListener(lifecycleListener);
        }
        for (Valve valve : this.contextValves) {
            context.getPipeline().addValve(valve);
        }
        for (ErrorPage errorPage : getErrorPages()) {
            new TomcatErrorPage(errorPage).addToContext(context);
        }
        for (MimeMappings.Mapping mapping : getMimeMappings()) {
            context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
        }
        long sessionTimeout = getSessionTimeout();
        if (sessionTimeout > 0) {
            // Tomcat timeouts are in minutes
            sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L);
        }
        context.setSessionTimeout((int) sessionTimeout);
        for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
            customizer.customize(context);
        }
    }

    /**
     * Post process the Tomcat {@link Context} before it used with the Tomcat Server.
     * Subclasses can override this method to apply additional processing to the
     * {@link Context}.
     * @param context the Tomcat {@link Context}
     */
    protected void postProcessContext(Context context) {
    }

    /**
     * Factory method called to create the {@link TomcatEmbeddedServletContainer}.
     * Subclasses can override this method to return a different
     * {@link TomcatEmbeddedServletContainer} or apply additional processing to the Tomcat
     * server.
     * @param tomcat the Tomcat server.
     * @return a new {@link TomcatEmbeddedServletContainer} instance
     */
    protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
        return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
    }

    private File createTempDir(String prefix) {
        try {
            File tempFolder = File.createTempFile(prefix + ".", "." + getPort());
            tempFolder.delete();
            tempFolder.mkdir();
            tempFolder.deleteOnExit();
            return tempFolder;
        } catch (IOException ex) {
            throw new EmbeddedServletContainerException("Unable to create Tomcat tempdir. java.io.tmpdir is set to "
                    + System.getProperty("java.io.tmpdir"), ex);
        }
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    /**
     * Set the Tomcat base directory. If not specified a temporary directory will be used.
     * @param baseDirectory the tomcat base directory
     */
    public void setBaseDirectory(File baseDirectory) {
        this.baseDirectory = baseDirectory;
    }

    /**
     * A comma-separated list of jars to ignore for TLD scanning. See Tomcat's
     * catalina.properties for typical values. Defaults to a list drawn from that source.
     * @param tldSkip the jars to skip when scanning for TLDs etc
     */
    public void setTldSkip(String tldSkip) {
        Assert.notNull(tldSkip, "TldSkip must not be null");
        this.tldSkip = tldSkip;
    }

    /**
     * The Tomcat protocol to use when create the {@link Connector}.
     * @param protocol the protocol
     * @see Connector#Connector(String)
     */
    public void setProtocol(String protocol) {
        Assert.hasLength(protocol, "Protocol must not be empty");
        this.protocol = protocol;
    }

    /**
     * Set {@link Valve}s that should be applied to the Tomcat {@link Context}. Calling
     * this method will replace any existing listeners.
     * @param contextValves the valves to set
     */
    public void setContextValves(Collection<? extends Valve> contextValves) {
        Assert.notNull(contextValves, "Valves must not be null");
        this.contextValves = new ArrayList<Valve>(contextValves);
    }

    /**
     * Returns a mutable collection of the {@link Valve}s that will be applied to the
     * Tomcat {@link Context}.
     * @return the contextValves the valves that will be applied
     */
    public Collection<Valve> getValves() {
        return this.contextValves;
    }

    /**
     * Add {@link Valve}s that should be applied to the Tomcat {@link Context}.
     * @param contextValves the valves to add
     */
    public void addContextValves(Valve... contextValves) {
        Assert.notNull(contextValves, "Valves must not be null");
        this.contextValves.addAll(Arrays.asList(contextValves));
    }

    /**
     * Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context}
     * . Calling this method will replace any existing listeners.
     * @param contextLifecycleListeners the listeners to set
     */
    public void setContextLifecycleListeners(Collection<? extends LifecycleListener> contextLifecycleListeners) {
        Assert.notNull(contextLifecycleListeners, "ContextLifecycleListeners must not be null");
        this.contextLifecycleListeners = new ArrayList<LifecycleListener>(contextLifecycleListeners);
    }

    /**
     * Returns a mutable collection of the {@link LifecycleListener}s that will be applied
     * to the Tomcat {@link Context} .
     * @return the contextLifecycleListeners the listeners that will be applied
     */
    public Collection<LifecycleListener> getContextLifecycleListeners() {
        return this.contextLifecycleListeners;
    }

    /**
     * Add {@link LifecycleListener}s that should be added to the Tomcat {@link Context}.
     * @param contextLifecycleListeners the listeners to add
     */
    public void addContextLifecycleListeners(LifecycleListener... contextLifecycleListeners) {
        Assert.notNull(contextLifecycleListeners, "ContextLifecycleListeners must not be null");
        this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners));
    }

    /**
     * Set {@link TomcatContextCustomizer}s that should be applied to the Tomcat
     * {@link Context} . Calling this method will replace any existing customizers.
     * @param tomcatContextCustomizers the customizers to set
     */
    public void setTomcatContextCustomizers(
            Collection<? extends TomcatContextCustomizer> tomcatContextCustomizers) {
        Assert.notNull(tomcatContextCustomizers, "TomcatContextCustomizers must not be null");
        this.tomcatContextCustomizers = new ArrayList<TomcatContextCustomizer>(tomcatContextCustomizers);
    }

    /**
     * Returns a mutable collection of the {@link TomcatContextCustomizer}s that will be
     * applied to the Tomcat {@link Context} .
     * @return the listeners that will be applied
     */
    public Collection<TomcatContextCustomizer> getTomcatContextCustomizers() {
        return this.tomcatContextCustomizers;
    }

    /**
     * Add {@link TomcatContextCustomizer}s that should be added to the Tomcat
     * {@link Context}.
     * @param tomcatContextCustomizers the customizers to add
     */
    public void addContextCustomizers(TomcatContextCustomizer... tomcatContextCustomizers) {
        Assert.notNull(tomcatContextCustomizers, "TomcatContextCustomizers must not be null");
        this.tomcatContextCustomizers.addAll(Arrays.asList(tomcatContextCustomizers));
    }

    /**
     * Set {@link TomcatConnectorCustomizer}s that should be applied to the Tomcat
     * {@link Connector} . Calling this method will replace any existing customizers.
     * @param tomcatConnectorCustomizers the customizers to set
     */
    public void setTomcatConnectorCustomizers(
            Collection<? extends TomcatConnectorCustomizer> tomcatConnectorCustomizers) {
        Assert.notNull(tomcatConnectorCustomizers, "TomcatConnectorCustomizers must not be null");
        this.tomcatConnectorCustomizers = new ArrayList<TomcatConnectorCustomizer>(tomcatConnectorCustomizers);
    }

    /**
     * Add {@link TomcatContextCustomizer}s that should be added to the Tomcat
     * {@link Connector}.
     * @param tomcatConnectorCustomizers the customizers to add
     */
    public void addConnectorCustomizers(TomcatConnectorCustomizer... tomcatConnectorCustomizers) {
        Assert.notNull(tomcatConnectorCustomizers, "TomcatConnectorCustomizers must not be null");
        this.tomcatConnectorCustomizers.addAll(Arrays.asList(tomcatConnectorCustomizers));
    }

    /**
     * Returns a mutable collection of the {@link TomcatConnectorCustomizer}s that will be
     * applied to the Tomcat {@link Context} .
     * @return the listeners that will be applied
     */
    public Collection<TomcatConnectorCustomizer> getTomcatConnectorCustomizers() {
        return this.tomcatConnectorCustomizers;
    }

    /**
     * Add {@link Connector}s in addition to the default connector, e.g. for SSL or AJP
     * @param connectors the connectors to add
     */
    public void addAdditionalTomcatConnectors(Connector... connectors) {
        Assert.notNull(connectors, "Connectors must not be null");
        this.additionalTomcatConnectors.addAll(Arrays.asList(connectors));
    }

    /**
     * Returns a mutable collection of the {@link Connector}s that will be added to the
     * Tomcat
     * @return the additionalTomcatConnectors
     */
    public List<Connector> getAdditionalTomcatConnectors() {
        return this.additionalTomcatConnectors;
    }

    /**
     * Set the character encoding to use for URL decoding. If not specified 'UTF-8' will
     * be used.
     * @param uriEncoding the uri encoding to set
     */
    public void setUriEncoding(String uriEncoding) {
        this.uriEncoding = uriEncoding;
    }

    /**
     * Returns the character encoding to use for URL decoding.
     * @return the URI encoding
     */
    public String getUriEncoding() {
        return this.uriEncoding;
    }

    private static class TomcatErrorPage {

        private final String location;

        private final String exceptionType;

        private final int errorCode;

        private final Object nativePage;

        public TomcatErrorPage(ErrorPage errorPage) {
            this.location = errorPage.getPath();
            this.exceptionType = errorPage.getExceptionName();
            this.errorCode = errorPage.getStatusCode();
            this.nativePage = createNativePage(errorPage);
        }

        private Object createNativePage(ErrorPage errorPage) {
            Object nativePage = null;
            try {
                if (ClassUtils.isPresent("org.apache.tomcat.util.descriptor.web.ErrorPage", null)) {
                    nativePage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
                } else if (ClassUtils.isPresent("org.apache.catalina.deploy.ErrorPage", null)) {
                    nativePage = BeanUtils
                            .instantiate(ClassUtils.forName("org.apache.catalina.deploy.ErrorPage", null));
                }
            } catch (ClassNotFoundException ex) {
                // Swallow and continue
            } catch (LinkageError ex) {
                // Swallow and continue
            }
            return nativePage;
        }

        public void addToContext(Context context) {
            Assert.state(this.nativePage != null, "Neither Tomcat 7 nor 8 detected so no native error page exists");
            if (ClassUtils.isPresent("org.apache.tomcat.util.descriptor.web.ErrorPage", null)) {
                org.apache.tomcat.util.descriptor.web.ErrorPage errorPage = (org.apache.tomcat.util.descriptor.web.ErrorPage) this.nativePage;
                errorPage.setLocation(this.location);
                errorPage.setErrorCode(this.errorCode);
                errorPage.setExceptionType(this.exceptionType);
                context.addErrorPage(errorPage);
            } else {
                callMethod(this.nativePage, "setLocation", this.location, String.class);
                callMethod(this.nativePage, "setErrorCode", this.errorCode, int.class);
                callMethod(this.nativePage, "setExceptionType", this.exceptionType, String.class);
                callMethod(context, "addErrorPage", this.nativePage, this.nativePage.getClass());
            }
        }

        private void callMethod(Object target, String name, Object value, Class<?> type) {
            Method method = ReflectionUtils.findMethod(target.getClass(), name, type);
            ReflectionUtils.invokeMethod(method, target, value);
        }

    }

    /**
     * {@link LifecycleListener} that stores an empty merged web.xml. This is critical for
     * Jasper to prevent warnings about missing web.xml files and to enable EL.
     */
    private static class StoreMergedWebXmlListener implements LifecycleListener {

        private final String MERGED_WEB_XML = org.apache.tomcat.util.scan.Constants.MERGED_WEB_XML;

        @Override
        public void lifecycleEvent(LifecycleEvent event) {
            if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                onStart((Context) event.getLifecycle());
            }
        }

        private void onStart(Context context) {
            ServletContext servletContext = context.getServletContext();
            if (servletContext.getAttribute(this.MERGED_WEB_XML) == null) {
                servletContext.setAttribute(this.MERGED_WEB_XML, getEmptyWebXml());
            }
            TomcatResources.get(context).addClasspathResources();
        }

        private String getEmptyWebXml() {
            InputStream stream = SciTomcatEmbeddedServletContainerFactory.class
                    .getResourceAsStream("empty-web.xml");
            Assert.state(stream != null, "Unable to read empty web.xml");
            try {
                try {
                    return StreamUtils.copyToString(stream, Charset.forName("UTF-8"));
                } finally {
                    stream.close();
                }
            } catch (IOException ex) {
                throw new IllegalStateException(ex);
            }
        }

    }

}