Java tutorial
/* 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(); } }