wicket.protocol.http.WicketFilter.java Source code

Java tutorial

Introduction

Here is the source code for wicket.protocol.http.WicketFilter.java

Source

/*
 * $Id: org.eclipse.jdt.ui.prefs 5004 2006-03-17 20:47:08 -0800 (Fri, 17 Mar
 * 2006) eelco12 $ $Revision: 5004 $ $Date: 2006-03-17 20:47:08 -0800 (Fri, 17
 * Mar 2006) $
 * 
 * ==============================================================================
 * 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 wicket.protocol.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import wicket.AbortException;
import wicket.Application;
import wicket.RequestCycle;
import wicket.Resource;
import wicket.Session;
import wicket.WicketRuntimeException;
import wicket.session.ISessionStore;
import wicket.settings.IRequestCycleSettings.RenderStrategy;
import wicket.util.resource.IResourceStream;
import wicket.util.string.Strings;
import wicket.util.time.Time;

/**
 * Filter for initiating handling of Wicket requests.
 * 
 * @author jcompagner
 */
public class WicketFilter implements Filter {
    /** Log. */
    private static final Log log = LogFactory.getLog(WicketFilter.class);

    /**
     * The name of the context parameter that specifies application factory
     * class
     */
    public static final String APP_FACT_PARAM = "applicationFactoryClassName";

    /**
     * The name of the root path parameter that specifies the root dir of the
     * app.
     */
    public static final String FILTER_PATH_PARAM = "filterPath";

    /** The URL path prefix expected for (so called) resources (not html pages). */
    private static final String RESOURCES_PATH_PREFIX = "/resources/";

    /**
     * The servlet path holder when the WicketSerlvet is used. So that the
     * filter path will be computed with the first request. Note: This variable
     * is by purpose package protected. See WicketServlet
     */
    static final String SERVLET_PATH_HOLDER = "<servlet>";

    /** See javax.servlet.FilterConfig */
    private FilterConfig filterConfig;

    /**
     * This is the filter path that can be specified in the filter config. Or it
     * is the servlet path if the wicket servlet it used. both are without any /
     * (start or end)
     */
    private String filterPath;

    /**
     * This holds the complete full root path including context and filter or
     * servlet path
     */
    private String rootPath;

    /** The Wicket Application associated with the Filter */
    private WebApplication webApplication;

    /**
     * Servlet cleanup.
     */
    public void destroy() {
        this.webApplication.internalDestroy();
        this.webApplication = null;
    }

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        if (isWicketRequest(httpServletRequest)) {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            long lastModified = getLastModified(httpServletRequest);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(httpServletRequest, httpServletResponse);
            } else {
                long ifModifiedSince = httpServletRequest.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(httpServletResponse, lastModified);
                    doGet(httpServletRequest, httpServletResponse);
                } else {
                    httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }
        } else {
            chain.doFilter(request, response);
        }
    }

    /**
     * Handles servlet page requests.
     * 
     * @param servletRequest
     *            Servlet request object
     * @param servletResponse
     *            Servlet response object
     * @throws ServletException
     *             Thrown if something goes wrong during request handling
     * @throws IOException
     */
    public final void doGet(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse)
            throws ServletException, IOException {
        // First, set the webapplication for this thread
        Application.set(webApplication);

        // Create a new webrequest
        final WebRequest request = webApplication.newWebRequest(servletRequest);

        if (webApplication.getRequestCycleSettings().getRenderStrategy() == RenderStrategy.REDIRECT_TO_BUFFER) {
            String queryString = servletRequest.getQueryString();
            if (!Strings.isEmpty(queryString)) {
                // Try to see if there is a redirect stored
                ISessionStore sessionStore = webApplication.getSessionStore();
                String sessionId = sessionStore.getSessionId(request, false);
                if (sessionId != null) {
                    BufferedHttpServletResponse bufferedResponse = webApplication.popBufferedResponse(sessionId,
                            queryString);

                    if (bufferedResponse != null) {
                        bufferedResponse.writeTo(servletResponse);
                        // redirect responses are ignored for the request
                        // logger...
                        return;
                    }
                }
            }
        }

        // If the request does not provide information about the encoding of its
        // body (which includes POST parameters), than assume the default
        // encoding as defined by the wicket application. Bear in mind that the
        // encoding of the request usually is equal to the previous response.
        // However it is a known bug of IE that it does not provide this
        // information. Please see the wiki for more details and why all other
        // browser deliberately copied that bug.
        if (servletRequest.getCharacterEncoding() == null) {
            try {
                // The encoding defined by the wicket settings is used to encode
                // the responses. Thus, it is reasonable to assume the request
                // has the same encoding. This is especially important for
                // forms and form parameters.
                servletRequest.setCharacterEncoding(
                        webApplication.getRequestCycleSettings().getResponseRequestEncoding());
            } catch (UnsupportedEncodingException ex) {
                throw new WicketRuntimeException(ex.getMessage());
            }
        }

        // Get session for request
        final WebSession session = webApplication.getSession(request);

        // Create a response object and set the output encoding according to
        // wicket's application setttings.
        final WebResponse response = webApplication.newWebResponse(servletResponse);
        response.setAjax(request.isAjax());
        response.setCharacterEncoding(webApplication.getRequestCycleSettings().getResponseRequestEncoding());

        try {
            RequestCycle cycle = session.newRequestCycle(request, response);
            try {
                // Process request
                cycle.request();
            } catch (AbortException e) {
                // noop
            }
        } finally {
            // Close response
            response.close();

            // Clean up thread local session
            Session.unset();

            // Clean up thread local application
            Application.unset();
        }
    }

    /**
     * @return The filter config of this WicketFilter
     */
    public FilterConfig getFilterConfig() {
        return filterConfig;
    }

    /**
     * Returns the full rootpath of this application. This is the
     * ApplicationSettings.contextpath and the WicketFilter.rootpath concatted.
     * 
     * @param request
     *            The request where the optional context path (if not specified
     *            in the settings) can be get from.
     * 
     * @return String the full rootpath.
     */
    public String getRootPath(HttpServletRequest request) {
        if (rootPath == null) {
            String contextPath = webApplication.getApplicationSettings().getContextPath();
            if (contextPath == null) {
                contextPath = request.getContextPath();
                if (contextPath == null)
                    contextPath = "";
            }

            if (SERVLET_PATH_HOLDER.equals(filterPath)) {
                filterPath = request.getServletPath();
                if (filterPath.startsWith("/")) {
                    filterPath = filterPath.substring(1);
                }
            }
            if (!contextPath.endsWith("/")) {
                rootPath = contextPath + "/" + filterPath;
            } else {
                rootPath = contextPath + filterPath;
            }
        }
        return rootPath;
    }

    /**
     * 
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;

        IWebApplicationFactory factory = getApplicationFactory();

        // Construct WebApplication subclass
        this.webApplication = factory.createApplication(this);

        // Set this WicketServlet as the servlet for the web application
        this.webApplication.setWicketFilter(this);

        // Store instance of this application object in servlet context to make
        // integration with outside world easier
        String contextKey = "wicket:" + filterConfig.getFilterName();
        filterConfig.getServletContext().setAttribute(contextKey, this.webApplication);

        filterPath = filterConfig.getInitParameter(FILTER_PATH_PARAM);

        try {
            Application.set(webApplication);

            // Call internal init method of web application for default
            // initialisation
            this.webApplication.internalInit();

            // Call init method of web application
            this.webApplication.init();

            // We initialize components here rather than in the constructor or
            // in the internal init, because in the init method class aliases
            // can be added, that would be used in installing resources in the
            // component.
            this.webApplication.initializeComponents();

            // Finished
            log.info("Wicket application " + this.webApplication.getName() + " started [factory="
                    + factory.getClass().getName() + "]");
        } finally {
            Application.unset();
        }
    }

    /**
     * Creates the web application factory instance.
     * 
     * If no APP_FACT_PARAM is specified in web.xml
     * ContextParamWebApplicationFactory will be used by default.
     * 
     * @see ContextParamWebApplicationFactory
     * 
     * @return application factory instance
     */
    protected IWebApplicationFactory getApplicationFactory() {
        final String appFactoryClassName = filterConfig.getInitParameter(APP_FACT_PARAM);

        if (appFactoryClassName == null) {
            // If no context param was specified we return the default factory
            return new ContextParamWebApplicationFactory();
        } else {
            try {
                // Try to find the specified factory class
                final Class factoryClass = getClass().getClassLoader().loadClass(appFactoryClassName);

                // Instantiate the factory
                return (IWebApplicationFactory) factoryClass.newInstance();
            } catch (ClassCastException e) {
                throw new WicketRuntimeException("Application factory class " + appFactoryClassName
                        + " must implement IWebApplicationFactory");
            } catch (ClassNotFoundException e) {
                throw new WebApplicationFactoryCreationException(appFactoryClassName, e);
            } catch (InstantiationException e) {
                throw new WebApplicationFactoryCreationException(appFactoryClassName, e);
            } catch (IllegalAccessException e) {
                throw new WebApplicationFactoryCreationException(appFactoryClassName, e);
            } catch (SecurityException e) {
                throw new WebApplicationFactoryCreationException(appFactoryClassName, e);
            }
        }
    }

    /**
     * Gets the last modified time stamp for the given request.
     * 
     * @param servletRequest
     * @return The last modified time stamp
     */
    long getLastModified(final HttpServletRequest servletRequest) {
        final String pathInfo = servletRequest.getRequestURI();

        int rootPathLength = getRootPath(servletRequest).length();
        if (pathInfo.length() > rootPathLength
                && pathInfo.substring(rootPathLength).startsWith(RESOURCES_PATH_PREFIX)) {
            final String resourceReferenceKey = pathInfo.substring(rootPathLength + RESOURCES_PATH_PREFIX.length());

            // Try to find shared resource
            Resource resource = webApplication.getSharedResources().get(resourceReferenceKey);

            // If resource found and it is cacheable
            if ((resource != null) && resource.isCacheable()) {
                try {
                    Application.set(webApplication);

                    final WebRequest webRequest = webApplication.newWebRequest(servletRequest);

                    // Set parameters from servlet request
                    resource.setParameters(webRequest.getParameterMap());

                    // Get resource stream
                    IResourceStream stream = resource.getResourceStream();

                    // Get last modified time from stream
                    Time time = stream.lastModifiedTime();

                    try {
                        stream.close();
                    } catch (IOException e) {
                        // ignore
                    }

                    return time != null ? time.getMilliseconds() : -1;
                } catch (AbortException e) {
                    return -1;
                } finally {
                    resource.setParameters(null);
                    Application.unset();
                }
            }
        }
        return -1;
    }

    /**
     * Is this a Wicket request?
     * 
     * @param request
     *            The servlet request
     * @return True if this is a Wicket request
     */
    private boolean isWicketRequest(HttpServletRequest request) {
        String fullRootPath = getRootPath(request);
        String url = request.getRequestURI();
        // Homepage
        if (url.startsWith(fullRootPath)) {
            // url == fullRootPath
            if (url.length() == fullRootPath.length()) {
                return true;
            }
            //  
            if ((url.length() > fullRootPath.length()) && (url.charAt(fullRootPath.length()) == ';')) {
                return true;
            }
        }
        // SharedResources
        String tmp = Strings.join("/", fullRootPath, RESOURCES_PATH_PREFIX);
        if (url.startsWith(tmp)) {
            return true;
        }
        // Mounted url
        String path = null;
        if (fullRootPath.length() < url.length()) {
            path = url.substring(fullRootPath.length());
        } else {
            path = url;
        }

        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return webApplication.getRequestCycleProcessor().getRequestCodingStrategy()
                .urlCodingStrategyForPath(path) != null;
    }

    /**
     * If the response has not already a 'lastModified' header set and if
     * 'lastModified' >= 0 than set the response header accordingly.
     * 
     * @param resp
     * @param lastModified
     */
    private void maybeSetLastModified(final HttpServletResponse resp, final long lastModified) {
        if (resp.containsHeader("Last-Modified")) {
            return;
        }
        if (lastModified >= 0) {
            resp.setDateHeader("Last-Modified", lastModified);
        }
    }
}