All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.grizzly.servlet.ServletHandler Maven / Gradle / Ivy

There is a newer version: 4.1.0-M1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2008-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.grizzly.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.DispatcherType;
import static javax.servlet.DispatcherType.REQUEST;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.Note;
import org.glassfish.grizzly.http.server.AfterServiceListener;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.server.SessionManager;
import org.glassfish.grizzly.http.server.util.ClassLoaderUtil;
import org.glassfish.grizzly.http.server.util.DispatcherHelper;
import org.glassfish.grizzly.http.server.util.Globals;
import org.glassfish.grizzly.http.server.util.HtmlHelper;
import org.glassfish.grizzly.http.server.util.MappingData;
import org.glassfish.grizzly.http.util.CharChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpRequestURIDecoder;
import org.glassfish.grizzly.http.util.HttpStatus;

/**
 * HttpHandler implementation that provides an entry point for processing
 * a Servlet request.
 * 
 * @author Jeanfrancois Arcand
 */
public class ServletHandler extends HttpHandler {

    private static final Logger LOGGER = Grizzly.logger(ServletHandler.class);

    static final Note SERVLET_REQUEST_NOTE =
            Request.createNote(HttpServletRequestImpl.class.getName());
    static final Note SERVLET_RESPONSE_NOTE =
            Request.createNote(HttpServletResponseImpl.class.getName());

    static final ServletAfterServiceListener servletAfterServiceListener =
            new ServletAfterServiceListener();

    protected String servletClassName;
    protected Class servletClass;
    protected volatile Servlet servletInstance = null;
    private String contextPath = "";
    private final Object lock = new Object();


    /**
     * The {@link WebappContext}
     */
    private final WebappContext servletCtx;
    /**
     * The {@link ServletConfigImpl}
     */
    private final ServletConfigImpl servletConfig;
    /**
     * The {@link SessionManager}
     */
    private SessionManager sessionManager = ServletSessionManager.instance();
    /**
     * Holder for our configured properties.
     */
    protected final Map properties = new HashMap();
    /**
     * Initialize the {@link ServletContext}
     */
    protected boolean initialize = true;
    protected ClassLoader classLoader;

    protected ExpectationHandler expectationHandler;

    protected FilterChainFactory filterChainFactory;

    // Listeners to be invoked when ServletHandler.destroy is called
    private List onDestroyListeners;

    // ------------------------------------------------------------ Constructors


    protected ServletHandler(final ServletConfigImpl servletConfig) {
        this.servletConfig = servletConfig;
        servletCtx = (WebappContext) servletConfig.getServletContext();
    }

    // ---------------------------------------------------------- Public Methods

    /**
     * {@inheritDoc}
     */
    @Override
    public void start() {
        try {
            configureServletEnv();
        } catch (Throwable t) {
            LOGGER.log(Level.SEVERE, "start", t);
        }
    }

    /**
     * Override parent's {@link HttpHandler#sendAcknowledgment(org.glassfish.grizzly.http.server.Request, org.glassfish.grizzly.http.server.Response)}
     * to let {@link ExpectationHandler} (if one is registered) process the expectation.
     */
    @Override
    protected boolean sendAcknowledgment(Request request, Response response)
            throws IOException {
        return expectationHandler != null
                || super.sendAcknowledgment(request, response);

    }


    /**
     * {@inheritDoc}
     */
    @Override
    public void service(Request request, Response response) throws Exception {
        if (classLoader != null) {
            final ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader();
            Thread.currentThread().setContextClassLoader(classLoader);
            try {
                doServletService(request, response);
            } finally {
                Thread.currentThread().setContextClassLoader(prevClassLoader);
            }
        } else {
            doServletService(request, response);
        }
    }

    protected void doServletService(final Request request, final Response response) {
        try {
            final String uri = request.getRequestURI();

            // The request is not for us.
            if (contextPath.length() > 0 && !uri.startsWith(contextPath)) {
                customizeErrorPage(response, "Resource Not Found", 404, null);
                return;
            }

            final HttpServletRequestImpl servletRequest = HttpServletRequestImpl.create();
            final HttpServletResponseImpl servletResponse = HttpServletResponseImpl.create();

            setPathData(request, servletRequest);
            servletRequest.initialize(request, servletResponse, servletCtx);
            servletResponse.initialize(response, servletRequest);

            request.setNote(SERVLET_REQUEST_NOTE, servletRequest);
            request.setNote(SERVLET_RESPONSE_NOTE, servletResponse);

            request.addAfterServiceListener(servletAfterServiceListener);
            
            loadServlet();

            setDispatcherPath(request, getCombinedPath(servletRequest));

            //TODO: Make this configurable.
            servletResponse.addHeader(Header.Server.toString(), "grizzly/" + Grizzly.getDotedVersion());
            
            if (expectationHandler != null) {
                final AckActionImpl ackAction = new AckActionImpl(response);
                expectationHandler.onExpectAcknowledgement(servletRequest,
                        servletResponse, ackAction);
                if (!ackAction.isAcknowledged()) {
                    ackAction.acknowledge();
                } else if (ackAction.isFailAcknowledgement()) {
                    return;
                }
            }
            
            FilterChainInvoker filterChain = getFilterChain(request);
            if (filterChain != null) {
                filterChain.invokeFilterChain(servletRequest, servletResponse);
            } else {
                servletInstance.service(servletRequest, servletResponse);
            }

            // Request may want to initialize async processing
            servletRequest.onAfterService();
        } catch (Throwable ex) {
            LOGGER.log(Level.SEVERE, "service exception:", ex);
            customizeErrorPage(response, "Internal Error", 500, ex);
        }
    }

    protected FilterChainInvoker getFilterChain(Request request) {
        if (filterChainFactory != null) {
            return filterChainFactory.createFilterChain(request, servletInstance, REQUEST);
        }
        return null;
    }

    private void setDispatcherPath(final Request request, final String path) {
        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, path);
    }

    /**
     * Combines the servletPath and the pathInfo.
     *
     * If pathInfo is null, it is ignored. If servletPath
     * is null, then null is returned.
     *
     * @return The combined path with pathInfo appended to servletInfo
     */
    private String getCombinedPath(final HttpServletRequest request) {
        if (request.getServletPath() == null) {
            return null;
        }
        if (request.getPathInfo() == null) {
            return request.getServletPath();
        }
        return request.getServletPath() + request.getPathInfo();
    }

    protected void setPathData(final Request from,
                               final HttpServletRequestImpl to) {

        final MappingData data = from.obtainMappingData();
        to.setServletPath(data.wrapperPath.toString());
        to.setPathInfo(data.pathInfo.toString());
        to.setContextPath(data.contextPath.toString());
    }

    void doServletService(final ServletRequest servletRequest,
                          final ServletResponse servletResponse,
                          final DispatcherType dispatcherType)
            throws IOException, ServletException {
        try {
            loadServlet();
            FilterChainImpl filterChain =
                    filterChainFactory.createFilterChain(servletRequest,
                                                         servletInstance,
                                                         dispatcherType);
            if (filterChain != null) {
                filterChain.invokeFilterChain(servletRequest, servletResponse);
            } else {
                servletInstance.service(servletRequest, servletResponse);
            }
        } catch (ServletException se) {
            LOGGER.log(Level.SEVERE, "service exception:", se);
            throw se;
        } catch (IOException ie) {
            LOGGER.log(Level.SEVERE, "service exception:", ie);
            throw ie;
        }
    }

    /**
     * Customize the error page returned to the client.
     * @param response  the {@link Response}
     * @param message   the HTTP error message
     * @param errorCode the error code.
     */
    public void customizeErrorPage(final Response response,
                                   final String message,
                                   final int errorCode,
                                   final Throwable t) {
        if (!response.isCommitted()) {
            try {
                HtmlHelper.setErrorAndSendErrorPage(response.getRequest(), response,
                        response.getErrorPageGenerator(), errorCode, message,
                        message, t);
            } catch (IOException ex) {
                // We are in a very bad shape. Ignore.
            }
        }
    }

    /**
     * Load a {@link Servlet} instance.
     *
     * @throws javax.servlet.ServletException If failed to
     * {@link Servlet#init(javax.servlet.ServletConfig)}.
     */
    protected void loadServlet() throws ServletException {

        if (servletInstance == null) {
            synchronized (lock) {
                if (servletInstance == null) {
                    Servlet newServletInstance;
                    if (servletClassName != null) {
                        newServletInstance = (Servlet) ClassLoaderUtil.load(servletClassName);
                    } else {
                        try {
                            newServletInstance = servletClass.newInstance();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                    LOGGER.log(Level.INFO, "Loading Servlet: {0}", newServletInstance.getClass().getName());
                    newServletInstance.init(servletConfig);
                    servletInstance = newServletInstance;
                }
            }
        }

    }

    /**
     * Configure the {@link WebappContext}
     * and {@link ServletConfigImpl}
     * 
     * @throws javax.servlet.ServletException Error while configuring
     * {@link Servlet}.
     */
    protected void configureServletEnv() throws ServletException {
        if (contextPath.length() > 0) {
            final CharChunk cc = new CharChunk();
            char[] ch = contextPath.toCharArray();
            cc.setChars(ch, 0, ch.length);
            HttpRequestURIDecoder.normalizeChars(cc);
            contextPath = cc.toString();
        }

        if ("".equals(contextPath)) {
            contextPath = "";
        }

    }

    /**
     * Return the {@link Servlet} instance used by this {@link ServletHandler}
     * @return {@link Servlet} instance.
     */
    @SuppressWarnings({"UnusedDeclaration"})
    public Servlet getServletInstance() {
        return servletInstance;
    }

    /**
     * Set the {@link Servlet} instance used by this {@link ServletHandler}
     * @param servletInstance an instance of Servlet.
     */
    protected void setServletInstance(Servlet servletInstance) {
        this.servletInstance = servletInstance;
    }

    protected void setServletClassName(final String servletClassName) {
        this.servletClassName = servletClassName;
    }

    protected void setServletClass(final Class servletClass) {
        this.servletClass = servletClass;
    }

    /**
     * Set the {@link SessionManager} instance used by this {@link ServletHandler}
     * @param sessionManager an implementation of SessionManager.
     */
    protected void setSessionManager(SessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    /**
     *
     * Returns the portion of the request URI that indicates the context
     * of the request. The context path always comes first in a request
     * URI. The path starts with a "/" character but does not end with a "/"
     * character. For servlets in the default (root) context, this method
     * returns "". The container does not decode this string.
     *
     * 

It is possible that a servlet container may match a context by * more than one context path. In such cases this method will return the * actual context path used by the request and it may differ from the * path returned by the * {@link javax.servlet.ServletContext#getContextPath()} method. * The context path returned by * {@link javax.servlet.ServletContext#getContextPath()} * should be considered as the prime or preferred context path of the * application. * * @return a String specifying the * portion of the request URI that indicates the context * of the request * * @see javax.servlet.ServletContext#getContextPath() */ public String getContextPath() { return contextPath; } /** * Programmatically set the context path of the Servlet. * * @param contextPath Context path. */ public void setContextPath(String contextPath) { this.contextPath = contextPath; } /** * Destroy this Servlet and its associated * {@link javax.servlet.ServletContextListener} */ @Override public void destroy() { try { if (classLoader != null) { ClassLoader prevClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(classLoader); try { super.destroy(); if (servletInstance != null) { servletInstance.destroy(); servletInstance = null; } } finally { Thread.currentThread().setContextClassLoader(prevClassLoader); } } else { super.destroy(); } } finally { // notify destroy listeners (if any) if (onDestroyListeners != null) { for (int i = 0; i < onDestroyListeners.size(); i++) { try { onDestroyListeners.get(i).run(); } catch (Throwable t) { LOGGER.log(Level.WARNING, "onDestroyListener error", t); } } onDestroyListeners = null; } } } protected WebappContext getServletCtx() { return servletCtx; } public ClassLoader getClassLoader() { return classLoader; } public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } public ServletConfigImpl getServletConfig() { return servletConfig; } @Override public String getName() { return servletConfig.getServletName(); } /** * Get the {@link ExpectationHandler} responsible for processing * Expect: header (for example "Expect: 100-Continue"). * * @return the {@link ExpectationHandler} responsible for processing * Expect: header (for example "Expect: 100-Continue"). */ public ExpectationHandler getExpectationHandler() { return expectationHandler; } /** * Set the {@link ExpectationHandler} responsible for processing * Expect: header (for example "Expect: 100-Continue"). * * @param expectationHandler the {@link ExpectationHandler} responsible * for processing Expect: header (for example "Expect: 100-Continue"). */ public void setExpectationHandler(ExpectationHandler expectationHandler) { this.expectationHandler = expectationHandler; } @Override protected void setDispatcherHelper( final DispatcherHelper dispatcherHelper ) { servletCtx.setDispatcherHelper( dispatcherHelper ); } protected void setFilterChainFactory(final FilterChainFactory filterChainFactory) { this.filterChainFactory = filterChainFactory; } /** * Overrides default (JSESSIONID) session cookie name. * @return the session cookie name */ @Override protected String getSessionCookieName() { return servletCtx.getSessionCookieConfig().getName(); } /** * @return Servlet-aware {@link SessionManager} */ @Override protected SessionManager getSessionManager(Request request) { final SessionManager sm = request.getHttpFilter() .getConfiguration().getSessionManager(); return sm != null ? sm : this.sessionManager; } /** * Adds the {@link Runnable}, which will be invoked when {@link #destroy()} is called. * @param r {@link Runnable}, which contains destroy listener logic. */ void addOnDestroyListener(final Runnable r) { if (onDestroyListeners == null) { onDestroyListeners = new ArrayList(2); } onDestroyListeners.add(r); } // ---------------------------------------------------------- Nested Classes /** * AfterServiceListener, which is responsible for recycle servlet request and response * objects. */ static final class ServletAfterServiceListener implements AfterServiceListener { @Override public void onAfterService(final Request request) { final HttpServletRequestImpl servletRequest = getServletRequest(request); final HttpServletResponseImpl servletResponse = getServletResponse(request); if (servletRequest != null) { servletRequest.recycle(); servletResponse.recycle(); } } } static final class AckActionImpl implements ExpectationHandler.AckAction { private boolean isAcknowledged; private boolean isFailAcknowledgement; private final Response response; private AckActionImpl(final Response response) { this.response = response; } @Override public void acknowledge() throws IOException { if (isAcknowledged) { throw new IllegalStateException("Already acknowledged"); } isAcknowledged = true; response.setStatus(HttpStatus.CONINTUE_100); response.sendAcknowledgement(); } @Override public void fail() throws IOException { if (isAcknowledged) { throw new IllegalStateException("Already acknowledged"); } isAcknowledged = true; isFailAcknowledgement = true; response.setStatus(HttpStatus.EXPECTATION_FAILED_417); response.finish(); } public boolean isAcknowledged() { return isAcknowledged; } public boolean isFailAcknowledgement() { return isFailAcknowledgement; } } static HttpServletRequestImpl getServletRequest(final Request request) { return request.getNote(SERVLET_REQUEST_NOTE); } static HttpServletResponseImpl getServletResponse(final Request request) { return request.getNote(SERVLET_RESPONSE_NOTE); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy