ca.myewb.frame.servlet.WrapperServlet.java Source code

Java tutorial

Introduction

Here is the source code for ca.myewb.frame.servlet.WrapperServlet.java

Source

/*
    
This file is part of OpenMyEWB.
    
OpenMyEWB is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
OpenMyEWB is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with OpenMyEWB.  If not, see <http://www.gnu.org/licenses/>.
    
OpenMyEWB is Copyright 2005-2009 Nicolas Kruchten (nicolas@kruchten.com), Francis Kung, Engineers Without Borders Canada, Michael Trauttmansdorff, Jon Fishbein, David Kadish
    
*/

package ca.myewb.frame.servlet;

import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.Logger;
import org.apache.velocity.Template;
import org.apache.velocity.context.Context;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import ca.myewb.frame.ErrorMessage;
import ca.myewb.frame.GetParamWrapper;
import ca.myewb.frame.HibernateUtil;
import ca.myewb.frame.Message;
import ca.myewb.frame.Helpers;
import ca.myewb.frame.Controller;
import ca.myewb.frame.Permissions;
import ca.myewb.frame.PostParamWrapper;
import ca.myewb.frame.RedirectionException;
import ca.myewb.frame.StickyMessage;
import ca.myewb.frame.StickyMessages;
import ca.myewb.frame.forms.Form;
import ca.myewb.model.PageModel;
import ca.myewb.model.UserModel;

public class WrapperServlet extends CachingVelocityServlet {
    private static ThreadLocal<Transaction> transactionHolder = new ThreadLocal<Transaction>();
    private static String frontPageCache = null;
    private static Calendar cacheCal = null;

    public Template handleRequest(HttpServletRequest request, HttpServletResponse response, Context ctx)
            throws Exception {
        ctx.put("renderStart", System.currentTimeMillis());
        final String defaultPath = Helpers.getDefaultURL();
        String requestURIwithDomain = Helpers.getDomain() + request.getRequestURI();

        Logger log = Logger.getLogger(this.getClass());
        log.info("****** (wrapper)"); //to look through logs more easily
        if (request.isSecure()) {
            log.info(request.getHeader("Referer") + " -> https://" + requestURIwithDomain);
        } else {
            log.info(request.getHeader("Referer") + " -> http://" + requestURIwithDomain);
        }
        log.info(request.getHeader("User-Agent"));

        // Break down URL to find appropriate controller
        String[] path = Helpers.getURIComponents(request.getRequestURI());

        try //make sure there are enough slashes, we're not in safemode etc
        {
            checkURL(request, ctx, defaultPath, log, path);
        } catch (RedirectionException re) {
            log.info("Clean redirect: " + re.getTargetURL());
            response.sendRedirect(re.getTargetURL());

            return null;
        }

        //ok, we can at least parse which controller they want
        String area = path[0].toLowerCase();
        String className = path[1].replaceAll("[^\\p{L}]*$", ""); //nuke (trailing) punctuation

        // Redirect all requests for favicon.ico (wish this could be done at the apache level)
        if (path[path.length - 1].equals("favicon.ico") || path[path.length - 1].equals("favicon.gif")) {
            log.info("Redirect: favicon.ico");
            response.sendRedirect(Helpers.getAppPrefix() + "/favicon.ico");
            return null;
        }

        //########################################
        //if we get this far, the request is looking OK, we can go deeper with session and appsession mgmt
        //########################################

        Session hibernateSession = HibernateUtil.currentSession();
        if (area.startsWith("actions")) //if it doesn't, we shouldn't be making db changes
        {
            transactionHolder.set(hibernateSession.beginTransaction());
        }

        HttpSession httpSession = request.getSession();

        // Retrieve message (here because the catch block needs to see message)
        Message message = (Message) httpSession.getAttribute("message");
        ctx.put("messages", message);
        httpSession.removeAttribute("message");

        try //this block catches redirects cleanly
        {
            UserModel user = WrapperServlet.getUser(defaultPath, log, hibernateSession, httpSession);
            httpSession.setAttribute("userid", new Integer(user.getId()));

            updateUserList(httpSession, user.getId(), user.getFirstname() + " " + user.getLastname(), log);

            if (httpSession.getAttribute("interpageVars") == null) {
                httpSession.setAttribute("interpageVars", new Hashtable());
            }

            if (httpSession.getAttribute("storedParams") == null) {
                httpSession.setAttribute("storedParams", new Hashtable<String, PostParamWrapper>());
            }

            if (httpSession.getAttribute("storedForms") == null) {
                httpSession.setAttribute("storedForms", new Hashtable<String, Form>());
            }

            //deal with security, make sure the page exists etc
            PageModel page = getPageIfVisible(request, defaultPath, log, className, area, hibernateSession,
                    httpSession, user);
            className = page.getName();

            // Load controller  
            Controller theController = getController(defaultPath, log, className, area, httpSession, user);

            if (!className.toLowerCase().equals("confirm") && !Helpers.isDevMode()) {
                //force correct protocol for non-confirm pages
                //this way we can use a confirmation on secure flows
                if (theController.secureAccessRequired() && !request.isSecure()) {
                    throw new RedirectionException("https://" + requestURIwithDomain);
                } else if (!theController.secureAccessRequired() && request.isSecure()) {
                    throw new RedirectionException("http://" + requestURIwithDomain);
                }
            }

            theController.setHibernateSession(hibernateSession);
            theController.setHttpSession(httpSession);
            theController.setRequestParams(new PostParamWrapper(request));
            theController.setUrlParams(new GetParamWrapper(path));
            theController.setCurrentUser(user);
            theController.setHttpRequest(request);
            theController.setHttpResponse(response);

            //strip out unneeded vars from session
            cleanInterPageVars(httpSession, theController);

            List<StickyMessage> messages = new LinkedList<StickyMessage>();
            if (httpSession.getAttribute("hideStickies") == null) {
                messages = (new StickyMessages(user, request.getRequestURI())).getMessages();
            }

            ctx.put("toolbars", new Vector()); //controller may replace this

            //this is the big important call
            theController.handle(ctx); //###########################################
            log.debug("Controller handler returned OK");

            //now that that's ok, add some general stuff to the context
            List<PageModel> menu = Permissions.visiblePages(user, area);

            if (!menu.contains(page)) {
                menu.add(page);
            }

            ctx.put("menu", menu);
            ctx.put("herepage", className);
            ctx.put("heretitle", page.getDisplayName());
            ctx.put("user", user);
            if (user.getUsername().equals("guest")) {
                ctx.put("isGuest", new Boolean(true));
            }
            ctx.put("stickyMessages", messages);
            ctx.put("localTemplate", area + "/" + className + ".vm");
            ctx.put("area", area);
            ctx.put("base", Helpers.getAppPrefix());
            ctx.put("helpers", new Helpers());
            ctx.put("perms", new Permissions());
            if (!ctx.containsKey("targetURL")) {
                ctx.put("targetURL", request.getRequestURI());
            }
        } catch (RedirectionException re) {
            try {
                // Retain old message on redirect unless a new one's been set
                if ((message != null) && (httpSession.getAttribute("message") == null)) {
                    httpSession.setAttribute("message", message);
                }
            } catch (IllegalStateException e) {
                ; //most likely this means that the session was invalidated by the controller
            }

            if (re.isRollbackRequested()) {
                if (WrapperServlet.transactionHolder.get().isActive())
                    WrapperServlet.transactionHolder.get().rollback();
            }

            log.info("Clean redirect: " + re.getTargetURL());
            response.sendRedirect(re.getTargetURL());

            return null;
        }

        response.setCharacterEncoding("UTF-8");

        return getTemplate("frame/wrapper.vm");
    }

    //########################################################
    //
    private Controller getController(final String defaultPath, Logger log, String className, String area,
            HttpSession httpSession, UserModel user) throws Exception {
        Controller theController = null;

        try {
            theController = (Controller) Class.forName("ca.myewb.controllers." + area + "." + className)
                    .newInstance();
        } catch (ClassNotFoundException e3) {
            httpSession.setAttribute("message",
                    (new ErrorMessage("The previously requested page didn't exist, sorry!")));
            log.warn("ClassNotFound: " + area + "." + className + ", for " + user.getUsername() + " - redirecting");
            throw new RedirectionException(defaultPath);
        }

        return theController;
    }

    private void cleanInterPageVars(HttpSession httpSession, Controller theController) {
        // Setting up interpage session information
        List<String> neededVars = theController.getNeededInterpageVars();
        Hashtable currentVars = (Hashtable) httpSession.getAttribute("interpageVars");
        Enumeration varNames = currentVars.keys();

        while (varNames.hasMoreElements()) {
            String theName = (String) varNames.nextElement();

            if (!neededVars.contains(theName)) {
                currentVars.remove(theName);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static void setInterpageVar(HttpSession httpSession, String name, Object var) {
        Hashtable<String, Object> hashtable = (Hashtable) httpSession.getAttribute("interpageVars");

        if (hashtable == null) {
            httpSession.setAttribute("interpageVars", new Hashtable());
            hashtable = (Hashtable) httpSession.getAttribute("interpageVars");
        }

        hashtable.put(name, var);
    }

    private PageModel getPageIfVisible(HttpServletRequest request, final String defaultPath, Logger log,
            String className, String area, Session hibernateSession, HttpSession httpSession, UserModel user)
            throws HibernateException, RedirectionException {
        // Check if request page exists and if we have permission to see it
        List results = hibernateSession
                .createQuery("FROM PageModel WHERE (name=:name OR oldName=:name) AND area=:area")
                .setString("name", className).setString("area", area).list();

        if (results.isEmpty()) {
            log.error("Classname " + className + " not found in db for " + user.getUsername() + " - redirecting");
            httpSession.setAttribute("message",
                    (new ErrorMessage("The previously requested page didn't exist, sorry!")));
            throw new RedirectionException(defaultPath);
        }

        PageModel page = (PageModel) results.get(0);

        if (!Permissions.canViewPage(user, page)) {
            // Trying to access a restricted page!
            if (user.getUsername().equals("guest")) {
                WrapperServlet.setInterpageVar(httpSession, "requestedURL", request.getRequestURI());
                httpSession.setAttribute("message", new Message("Please sign in to reach the page you requested"));

                log.info("Access restricted for Guest at " + area + "." + className
                        + ", prompting for login - redirecting");

                throw new RedirectionException(Controller.path + "/home/SignIn");
            } else {
                httpSession.setAttribute("message",
                        new ErrorMessage("You do not have access to the page you requested"));

                log.warn("Access restricted for " + user.getUsername() + " at " + area + "." + className
                        + " - redirecting");
                throw new RedirectionException(defaultPath);
            }
        }

        return page;
    }

    public static UserModel getUser(final String defaultPath, Logger log, Session hibernateSession,
            HttpSession httpSession) throws HibernateException, RedirectionException {
        UserModel user;
        Integer userid = (Integer) httpSession.getAttribute("userid");

        // New session: retrieve guest user from database
        if (httpSession.isNew() || (userid == null)) {
            httpSession.setMaxInactiveInterval(60 * 60); //increase session length to 1 hour
            userid = new Integer(1);
            log.debug("No userid found, forcing default user: guest...");
        }

        user = (UserModel) hibernateSession.get(UserModel.class, userid);

        if (user == null) {
            log.warn("invalid userid in session! userid=" + userid);
            throw new RedirectionException(defaultPath);
        }

        log.debug("User identified: " + user.getUsername());

        return user;
    }

    private void checkURL(HttpServletRequest request, Context ctx, final String defaultPath, Logger log,
            String[] path) throws RedirectionException {

        if (path.length < 2) {
            log.info("Not enough slashes in URL (" + request.getRequestURI() + ")");
            throw new RedirectionException(defaultPath);
        }

        if (path[0].equals("home") && path[1].equals("safemodetoken")) {
            log.info("Setting safemode token in session.");
            request.getSession().setAttribute("safemodetoken", "yes");
            throw new RedirectionException(defaultPath);
        }

        // are we in safe mode?
        if ((new File(this.getServletConfig().getServletContext().getRealPath("safe.html"))).exists()) {
            // do we have a token to get through?
            if (request.getSession().getAttribute("safemodetoken") == null) {
                //no, get bounced out
                log.info("We're in safemode and user has no token.");
                throw new RedirectionException(Helpers.getAppPrefix() + "/safe.html", true);
            } else {
                ctx.put("safemodeon", "yes");
            }
        }
    }

    protected void requestCleanup(HttpServletRequest request, HttpServletResponse response) {
        try {
            Transaction threadTransaction = WrapperServlet.transactionHolder.get();

            if (threadTransaction == null) {
                Logger.getLogger(this.getClass()).debug("Cleanup: no transaction to commit");
            } else if (threadTransaction.wasCommitted()) {
                Logger.getLogger(this.getClass()).debug("Cleanup: transaction already committed");
            } else if (threadTransaction.wasRolledBack()) {
                Logger.getLogger(this.getClass()).debug("Cleanup: transaction had been rolled back");
            } else {
                HibernateUtil.currentSession().flush();
                Logger.getLogger(this.getClass()).debug("Committing transaction");
                threadTransaction.commit();
            }

            HibernateUtil.closeSession();
        } catch (Exception e) {
            Logger.getLogger(this.getClass()).fatal("Wrapper cleanup error: " + e.toString(), e);
            try {
                response.sendError(500, e.toString());
            } catch (IOException e1) {
                Logger.getLogger(this.getClass()).fatal("Wrapper cleanup SUB-error: " + e1.toString(), e1);
            }
        }
    }

    protected void error(HttpServletRequest request, HttpServletResponse response, Exception cause)
            throws IOException {
        Logger.getLogger(this.getClass()).fatal("Fatal wrapper error: " + cause.toString(), cause);

        Throwable causeCause = cause.getCause();
        int i = 1;

        while (causeCause != null) {
            Logger.getLogger(this.getClass()).fatal("Fatal wrapper error cause " + i + ": " + causeCause.toString(),
                    causeCause);
            causeCause = causeCause.getCause();
            i++;
        }

        try {
            if (WrapperServlet.transactionHolder.get() != null) {
                if (WrapperServlet.transactionHolder.get().isActive())
                    WrapperServlet.transactionHolder.get().rollback();
            }
        } catch (HibernateException e) {
            Logger.getLogger(this.getClass()).error("Rollback error: " + e.toString(), e);
        } catch (Exception e) {
            Logger.getLogger(this.getClass()).error("Secondary rollback error: " + e.toString(), e); //no idea, generic secondary error!
        }

        if (Helpers.isDevMode()) {
            response.sendError(500, cause.toString());
        } else {
            //set something here which says which URL last threw an error
            //near the beginning of request processing, check if it was the default URL, if so, it'll likely happen again, right?
            //so we should redirect to a last-resort page, telling people to shut down their browser etc
            request.getSession().setAttribute("message", new ErrorMessage(
                    "We're sorry, but the previous request caused a server error and could not be completed. The system administrators have been automatically notified."));
            response.sendRedirect(Helpers.getDefaultURL());
        }
    }

    @SuppressWarnings("unchecked")
    public static void updateUserList(HttpSession httpSession, int userId, String usersName, Logger log) {
        // Update the who's online list
        Hashtable userList = (Hashtable) httpSession.getServletContext().getAttribute("userList");
        Hashtable userTime = (Hashtable) httpSession.getServletContext().getAttribute("userTime");

        // Expire old users - this is only done on an actual pageload
        // and not on AJAX-keepalive.  There's no point in running this after
        // an AJAX request as they're not viewing a new copy of the online list;
        // and we also reduce server load this way.
        //
        // We know whether we're in pageload vs AJAX by the usersName variable:
        // it will be NULL if it's AJAX, and an actual string if it's a pageload
        if (usersName != null) {
            long threshold = System.currentTimeMillis() - (15 * 60 * 1000);

            // Use toArray() instead of the more convenient iterator() to allow
            // concurrent modification of the Hashtable even while iterating it
            Object[] keys = userTime.keySet().toArray();
            for (int i = 0; i < keys.length; i++) {
                long activetime = ((Long) userTime.get(keys[i])).longValue();
                if (activetime < threshold) {
                    userList.remove(keys[i]);
                    userTime.remove(keys[i]);
                }
            }
        }

        // Re-add myself if I was expired, and update my active time
        if (userId != 1)
            userTime.put(userId, new Long(System.currentTimeMillis()));

        if (userId != 1 && usersName != null)
            userList.put(userId, usersName);
    }

    public boolean isCachable(HttpServletRequest request) {
        boolean isGuest = request.getSession().isNew() || request.getSession().getAttribute("userid") == null
                || request.getSession().getAttribute("userid").equals(1);

        if (isGuest) {
            String requestURI = request.getRequestURI().toLowerCase();
            boolean isFrontPage = requestURI.endsWith("/posts") || requestURI.endsWith("/posts/any")
                    || requestURI.endsWith("/posts/any/1");

            return isFrontPage;
        }

        return false;
    }

    public void saveOutputToCache(String fragment) {
        WrapperServlet.setFrontPageCache(fragment);
    }

    public String getCachedOutputIfFresh(HttpServletRequest request) {
        return WrapperServlet.getFrontPageCacheifFresh();
    }

    public static synchronized String getFrontPageCacheifFresh() {
        if (WrapperServlet.cacheCal == null) {
            return null;
        }

        Calendar treshHoldCal = GregorianCalendar.getInstance();
        treshHoldCal.add(Calendar.MINUTE, -30);
        if (WrapperServlet.cacheCal.before(treshHoldCal)) {
            return null;
        }

        return WrapperServlet.frontPageCache + " <!-- cached copy from "
                + WrapperServlet.cacheCal.getTime().toString() + " -->";
    }

    public static synchronized void setFrontPageCache(String fragment) {
        WrapperServlet.frontPageCache = fragment;
        WrapperServlet.cacheCal = GregorianCalendar.getInstance();
    }
}