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

nextapp.echo.webcontainer.WebContainerServlet Maven / Gradle / Ivy

The newest version!
/* 
 * This file is part of the Echo Web Application Framework (hereinafter "Echo").
 * Copyright (C) 2002-2009 NextApp, Inc.
 *
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 */

package nextapp.echo.webcontainer;

import nextapp.echo.app.ApplicationInstance;
import nextapp.echo.app.util.Log;
import nextapp.echo.app.util.Uid;
import nextapp.echo.webcontainer.service.AsyncMonitorService;
import nextapp.echo.webcontainer.service.BootService;
import nextapp.echo.webcontainer.service.NewInstanceService;
import nextapp.echo.webcontainer.service.ResourceService;
import nextapp.echo.webcontainer.service.SessionExpiredService;
import nextapp.echo.webcontainer.service.StaticTextService;
import nextapp.echo.webcontainer.service.SynchronizeService;
import nextapp.echo.webcontainer.service.WindowHtmlService;

import java.io.IOException;
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Web container HttpServlet implementation.
 * An Echo application should provide an derivative of this
 * class which is registered in the web application
 * deployment descriptor.
 */
public abstract class WebContainerServlet extends HttpServlet {


    
    /** A ThreadLocal reference to the Connection relevant to the current thread. */ 
    private static final ThreadLocal activeConnection = new ThreadLocal();
    
    /**
     * A flag indicating whether caching should be disabled for all services.
     * This flag is for testing purposes only, and should be disabled for
     * production use.
     */
    public static final boolean DISABLE_CACHING = false;
    
    /** Flag indicating whether client-side debug console should be enabled. */
    public static final boolean ENABLE_CLIENT_DEBUG_CONSOLE = true;
    
    /**
     * Constant for getInstanceMode() indicating that one instance (UserInstance of
     * the application should exist per session.
     * If the user visits the application in a different browser window, the state of the singleton instance will
     * be displayed.  The user will experience "window not synchronized" errors if the user attempts to operate
     * the same application from multiple windows.
     */
    public static final int INSTANCE_MODE_SINGLE = 0;
    
    /**
     * Constant for getInstanceMode() indicating that one instance (UserInstance of
     * the application should exist per session per client browser window.
     * If the user visits the application in a different browser window, a new instance will be created.
     */
    public static final int INSTANCE_MODE_WINDOW = 1;
    
    /** Request parameter identifying requested Service. */
    public static final String SERVICE_ID_PARAMETER = "sid";
    
    /** Request parameter identifying requested UserInstance. */
    public static final String USER_INSTANCE_ID_PARAMETER = "uiid";
    
    /**
     * Service identifier of the 'default' service. 
     * The 'default' service is rendered when a client makes a request
     * without a service identifier and a session DOES exist.
     */
    public static final String SERVICE_ID_DEFAULT = "Echo.Default";
    
    /** Service identifier of the blank document service. */
    public static final String SERVICE_ID_BLANK_DOCUMENT = "Echo.BlankDocument";
    
    /**
     * Service identifier of the 'new instance' service. 
     * The 'new instance' service is rendered when a client makes a request
     * without a service identifier and a session DOES NOT exist.
     */
    public static final String SERVICE_ID_NEW_INSTANCE = "Echo.NewInstance";
    
    /**
     * Service identifier of the 'session expired' service.
     * The 'session expired' service is rendered when a client makes a
     * request that has an identifier and is intended for an active session, 
     * but no session exists. 
     */
    public static final String SERVICE_ID_SESSION_EXPIRED = "Echo.Expired";
    
    /** Global handler for multipart/form-data encoded HTTP requests. */
    private static MultipartRequestWrapper multipartRequestWrapper;
    
    /** Time at which servlet was loaded by class loader. */
    private static final long startupTime = System.currentTimeMillis();
    
    /** Global ResourceRegistry. */
    private static final ResourceRegistry resources = new ResourceRegistry();
    
    /** Global ServiceRegistry. */
    private static final ServiceRegistry services = new ServiceRegistry();

    static {
        // Install bootstrap JavaScript service.
        BootService.install(services);
        
        // Register "Echo" package, add standard resources.
        resources.addPackage("Echo", "nextapp/echo/webcontainer/resource/");
        resources.add("Echo", "resource/Transparent.gif", ContentType.IMAGE_GIF);
        resources.add("Echo", "resource/Blank.html", ContentType.TEXT_HTML);
        
        // Add standard services.
        services.add(ResourceService.INSTANCE);
        services.add(new StaticTextService(SERVICE_ID_BLANK_DOCUMENT, "text/html", ""));
    }
    
    /**
     * An interface implemented by a supporting object that will handle 
     * multipart/form-data encoded HTTP requests.  This type of request is
     * required for file uploads.  Echo does not provide internal support
     * for file uploads, but instead provides hooks for file-upload handling
     * components.  
     */
    public static interface MultipartRequestWrapper {
    
        /**
         * Returns a replacement HttpServletRequest object that
         * may be used to handle a multipart/form-data encoded HTTP request.
         *
         * @param request The HTTP request provided from the servlet container
         *        that has multipart/form-data encoding.
         * @return An HTTP request that is capable of handling 
         *         multipart/form-data encoding.
         */
        public HttpServletRequest getWrappedRequest(HttpServletRequest request)
        throws IOException, ServletException;
    }

    /**
     * Returns a reference to the Connection that is 
     * relevant to the current thread, or null if no connection is relevant.
     * 
     * @return the relevant Connection
     */
    public static final Connection getActiveConnection() {
        return (Connection) activeConnection.get();
    }
    
    /**
     * Returns the multipart/form-data encoded HTTP request handler.
     * 
     * @return The multipart/form-data encoded HTTP request handler.
     * @see #setMultipartRequestWrapper
     */
    public static MultipartRequestWrapper getMultipartRequestWrapper() {
        return multipartRequestWrapper;
    }
    
    /**
     * Retrieves the global ResourceRegistry.
     * 
     * @return the global ResourceRegistry 
     */
    public static ResourceRegistry getResourceRegistry() {
        return resources;
    }
    
    /**
     * Retrieves the global ServiceRegistry.
     * 
     * @return The global ServiceRegistry.
     */
    public static ServiceRegistry getServiceRegistry() {
        return services;
    }
    
    /**
     * Sets the multipart/form-data encoded HTTP request handler.
     * The multipart request wrapper can only be set one time.  It should be set
     * in a static block of your Echo application.  This method will disregard
     * additional attempts to set the wrapper if the provided wrapper's class
     * is identical to the existing one.  If the wrapper is already set and the
     * new wrapper object's class is different or the wrapper is null, an
     * exception is thrown.
     *
     * @param multipartRequestWrapper The handler for multipart/form-data 
     *        encoded HTTP requests.
     * @throws IllegalStateException if the application attempts to change
     *        a previously set multipart request handler.
     */
    public static final void setMultipartRequestWrapper(MultipartRequestWrapper multipartRequestWrapper) {
        if (WebContainerServlet.multipartRequestWrapper == null) {
            WebContainerServlet.multipartRequestWrapper = multipartRequestWrapper;
        } else {
            if (multipartRequestWrapper == null || 
                    !WebContainerServlet.multipartRequestWrapper.getClass().getName().equals(
                    multipartRequestWrapper.getClass().getName())) {
                throw new IllegalStateException("MultipartRequestWrapper already set.");
            }
        }
    }
    
    /** Collection of JavaScript Services which should be initially loaded. */
    private List initScripts = null; 
    
    /** Collection of CSS style sheet Services which should be initially loaded. */
    private List initStyleSheets = null;
    
    private WebSocketConnectionHandler wsHandler = null;
    /**
     * Default constructor.
     */
    public WebContainerServlet() {
        super();
        services.add(NewInstanceService.INSTANCE);
        services.add(SessionExpiredService.INSTANCE);
        services.add(SynchronizeService.INSTANCE);
        services.add(WindowHtmlService.INSTANCE);
        services.add(AsyncMonitorService.INSTANCE);
    }

    public void init() throws ServletException {
        super.init();

        // Read servlet init parameters and update server configuration
        ServerConfiguration.adoptServletConfiguration(getInitParameterMap());
    }

    /**
     * Return the servlet's init parameters as a map.
     *
     * @return servlet init parameters as map
     */
    private Map getInitParameterMap() {
        Map initParameters = new HashMap();
        Enumeration parameterNames = getInitParameterNames();
        while (parameterNames.hasMoreElements()) {
            String name = (String) parameterNames.nextElement();
            initParameters.put(name, getInitParameter(name));
        }
        return initParameters;
    }

    /**
     * Adds a JavaScript service to be loaded at initialization.
     * 
     * @param service the service which will provide JavaScript content.
     */
    protected void addInitScript(Service service) {
        if (initScripts == null) {
            initScripts = new ArrayList();
        } else if (initScripts.contains(service)) {
            return;
        }
        
        services.add(service);
        initScripts.add(service);
    }
    
    /**
     * Adds a CSS style sheet to be loaded at initialization.
     * 
     * @param service the service which will provide the CSS content.
     */
    protected void addInitStyleSheet(Service service) {
        if (initStyleSheets == null) {
            initStyleSheets = new ArrayList();
        } else if (initStyleSheets.contains(service)) {
            return;
        }

        services.add(service);
        initStyleSheets.add(service);
    }
    
    protected final void setWebSocketConnectionHandler(WebSocketConnectionHandler handler) {
        this.wsHandler = handler;
        this.wsHandler.assignParent(this);
    }
    
    protected final void removeWebSocketConnectionHandler() {
        wsHandler = null;
    }
    
    public final boolean hasWebSocketConnectionHandler() {
        return wsHandler != null;
    }
    
    /**
     * Handles a GET request.
     *
     * @see #process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    public final void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws IOException, ServletException {
        process(request, response);
    }
    
    /**
     * Handles a POST request.
     *
     * @see #process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    public final void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws IOException, ServletException {
        process(request, response);
    }
    
    /**
     * Returns the service that corresponds to the specified Id.
     *
     * @param id The id of the service to return.
     * @return The service corresponding to the specified Id.
     */
    private static Service getService(String id, boolean hasInstance) {
        if (id == null) {
            if (hasInstance) {
                id = SERVICE_ID_DEFAULT;
            } else {
                id = SERVICE_ID_NEW_INSTANCE;
            }
        } else {
            if (!hasInstance) {
                id = SERVICE_ID_SESSION_EXPIRED;
            }
        }
        
        Service service = services.get(id);
        if (service == null) {
            if (SERVICE_ID_DEFAULT.equals(id)) {
                throw new RuntimeException("Service not registered: SERVICE_ID_DEFAULT");
            } else if (SERVICE_ID_NEW_INSTANCE.equals(id)) {
                throw new RuntimeException("Service not registered: SERVICE_ID_NEW_INSTANCE");
            } else if (SERVICE_ID_SESSION_EXPIRED.equals(id)) {
                throw new RuntimeException("Service not registered: SERVICE_ID_SESSION_EXPIRED");
            }
        }
        return service;
    }
    
    /**
     * Returns an iterator over initialization script services.
     * 
     * @return the iterator
     */
    public Iterator getInitScripts() {
        return initScripts == null ? null : Collections.unmodifiableCollection(initScripts).iterator();
    }
    
    /**
     * Returns an iterator over initialization script services.
     * 
     * @return the iterator
     */
    public Iterator getInitStyleSheets() {
        return initStyleSheets == null ? null : Collections.unmodifiableCollection(initStyleSheets).iterator();
    }

    /**
     * Returns the instance operating mode of the application, determining how the application will perform if it
     * is visited by multiple browser windows.
     * 
     * @return the operating mode, one of the following values:
     *         
    *
  • INSTANCE_MODE_SINGLE (the default) to allow only a single instance of the application
  • *
  • INSTANCE_MODE_WINDOW to allow multiple instances of the application per session, with new * instances created for new browser windows
  • *
*/ public int getInstanceMode() { return INSTANCE_MODE_SINGLE; } /** * Creates a new ApplicationInstance for visitor to an * application. * * @return a new ApplicationInstance */ public abstract ApplicationInstance newApplicationInstance(); /** * Processes an HTTP request and generates a response. * * @param request the incoming HttpServletRequest * @param response the outgoing HttpServletResponse */ protected void process(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { Connection conn = null; try { conn = new Connection(this, request, response); activeConnection.set(conn); String serviceId = request.getParameter(SERVICE_ID_PARAMETER); Service service = getService(serviceId, conn.getUserInstanceContainer() != null); if (service == null) { throw new ServletException("Service id \"" + serviceId + "\" not registered."); } int version = service.getVersion(); // Set caching directives. if ((!DISABLE_CACHING) && version != Service.DO_NOT_CACHE) { // Setting all of the following (possibly with the exception of "Expires") // are *absolutely critical* in order to ensure proper caching of resources // with Internet Explorer 6. Without "Last-Modified", IE6 appears to not // cache images properly resulting in an substantially greater than expected // performance impact. response.setHeader("Cache-Control", "max-age=3600, public"); response.setDateHeader("Expires", System.currentTimeMillis() + (3600 * 1000)); response.setDateHeader("Last-Modified", startupTime); } else { response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-store"); response.setHeader("Expires", "0"); } service.service(conn); } catch (ServletException ex) { processError(conn, request, response, ex); } catch (IOException ex) { processError(conn, request, response, ex); } catch (RuntimeException ex) { processError(conn, request, response, ex); } finally { activeConnection.set(null); } } /** * Exception handler for process() method. The current implementation writes an Exception ID to the client. * * @param request The current HTTP request in progress * @param response The HTTP response * @param ex The exception triggering this error handleing. * @throws IOException May be thrown on issues writing to the HTTP response. */ private void processError(Connection conn, HttpServletRequest request, HttpServletResponse response, Exception ex) throws IOException { if (conn != null) { try { conn.disposeUserInstance(); } catch (Exception ignore) { //do nothing... } } String exceptionId = Uid.generateUidString(); Log.log("Server Exception. ID: " + exceptionId, ex); response.setContentType("text/plain"); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("Server Exception. ID: " + exceptionId); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy