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

org.wings.session.SessionServlet Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2000,2005 wingS development team.
 *
 * This file is part of wingS (http://wingsframework.org).
 *
 * wingS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * Please see COPYING for the complete licence.
 */
package org.wings.session;

import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.StringTokenizer;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wings.ReloadManager;
import org.wings.RequestURL;
import org.wings.Resource;
import org.wings.SForm;
import org.wings.SFrame;
import org.wings.event.ExitVetoException;
import org.wings.event.SRequestEvent;
import org.wings.externalizer.ExternalizeManager;
import org.wings.externalizer.ExternalizedResource;
import org.wings.io.Device;
import org.wings.io.DeviceFactory;
import org.wings.io.ServletDevice;
import org.wings.io.StringBuilderDevice;
import org.wings.resource.ReloadResource;
import org.wings.resource.StringResource;
import org.wings.resource.UpdateResource;
import org.wings.script.JavaScriptListener;
import org.wings.script.ScriptListener;

/**
 * The servlet engine creates for each user a new HttpSession. This
 * HttpSession can be accessed by all Serlvets running in the engine. A
 * WingServlet creates one wings SessionServlet per HTTPSession and stores
 * it in its context.
 * 

As the SessionServlets acts as Wrapper for the WingsServlet, you can * access from there as used the ServletContext and the HttpSession. * Additionally the SessionServlet containts also the wingS-Session with * all important services and the superordinated SFrame. To this SFrame all * wings-Components and hence the complete application state is attached. * The developer can access from any place via the SessionManager a * reference to the wingS-Session. Additionally the SessionServlet * provides access to the all containing HttpSession. * * @author Armin Haaf */ public final class SessionServlet extends HttpServlet implements HttpSessionBindingListener { private final transient static Logger log = LoggerFactory.getLogger(SessionServlet.class); /** * The parent {@link WingServlet} */ protected transient HttpServlet parent = this; /** * The session. */ private transient /* --- ATTENTION! This disable session serialization! */ Session session; private boolean firstRequest = true; /** Refer to comment in {@link #doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)} */ private String exitSessionWorkaround; /** * Default constructor. */ protected SessionServlet() { } /** * Sets the parent servlet contianint this wings session * servlet (WingsServlet, delegating its requests to the SessionServlet). */ protected final void setParent(HttpServlet p) { if (p != null) parent = p; } public final Session getSession() { return session; } /** * Overrides the session set for setLocaleFromHeader by a request parameter. * Hence you can force the wings session to adopt the clients Locale. */ public final void setLocaleFromHeader(String... args) { if (args == null) return; for (String arg : args) { try { session.setLocaleFromHeader(Boolean.valueOf(arg)); } catch (Exception e) { log.error("setLocaleFromHeader", e); } } } /** * The Locale of the current wings session servlet is determined by * the locale transmitted by the browser. The request parameter *

LocaleFromHeader
can override the behaviour * of a wings session servlet to adopt the clients browsers Locale. * * @param req The request to determine the local from. */ protected final void handleLocale(HttpServletRequest req) { setLocaleFromHeader(req.getParameterValues("LocaleFromHeader")); if (session.getLocaleFromHeader()) { session.determineLocale(); } } // jetzt kommen alle Servlet Methoden, die an den parent deligiert // werden @Override public ServletContext getServletContext() { if (parent != this) return parent.getServletContext(); else return super.getServletContext(); } @Override public String getInitParameter(String name) { if (parent != this) return parent.getInitParameter(name); else return super.getInitParameter(name); } @Override public Enumeration getInitParameterNames() { if (parent != this) return parent.getInitParameterNames(); else return super.getInitParameterNames(); } /** * Delegates log messages to the according WingsServlet or alternativly * to the HttpServlet logger. * * @param msg The logmessage */ @Override public void log(String msg) { if (parent != this) parent.log(msg); else super.log(msg); } @Override public String getServletInfo() { if (parent != this) return parent.getServletInfo(); else return super.getServletInfo(); } @Override public ServletConfig getServletConfig() { if (parent != this) return parent.getServletConfig(); else return super.getServletConfig(); } // bis hierhin /** * init */ public final void init(ServletConfig config, HttpServletRequest request, HttpServletResponse response) throws ServletException { try { session = new Session(parent); SessionManager.setSession(session); // set request.url in session, if used in constructor of wings main classs //if (request.isRequestedSessionIdValid()) { // this will fire an event, if the encoding has changed .. session.setProperty("request.url", new RequestURL("", getSessionEncoding(response))); //} session.init(config, request, response); try { String mainClassName = config.getInitParameter("wings.mainclass"); Class mainClass = null; try { mainClass = Class.forName(mainClassName, true, Thread.currentThread() .getContextClassLoader()); } catch (ClassNotFoundException e) { // fallback, in case the servlet container fails to set the // context class loader. mainClass = Class.forName(mainClassName); } mainClass.newInstance(); } catch (Exception ex) { log.error("could not load wings.mainclass: " + config.getInitParameter("wings.mainclass"), ex); throw new ServletException(ex); } } finally { // The session was set by the constructor. After init we // expect that only doPost/doGet is called, which set the // session also. So remove it here. SessionManager.removeSession(); } } /** * this method references to * {@link #doGet(HttpServletRequest, HttpServletResponse)} */ @Override public final void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException { //value chosen to limit denial of service if (req.getContentLength() > session.getMaxContentLength() * 1024) { res.setContentType("text/html"); ServletOutputStream out = res.getOutputStream(); out.println("Too big"); out.println("

Error - content length > " + session.getMaxContentLength() + 'k'); out.println("

"); } else { doGet(req, res); } // sollte man den obigen Block nicht durch folgende Zeile ersetzen? //throw new RuntimeException("this method must never be called!"); // bsc: Wieso? } /** * Verarbeitet Informationen vom Browser: *
    *
  • setzt Locale *
  • Dispatch Get Parameter *
  • feuert Form Events *
* Ist synchronized, damit nur ein Frame gleichzeitig bearbeitet * werden kann. */ @Override public final synchronized void doGet(HttpServletRequest req, HttpServletResponse response) { // Special case: You double clicked i.e. a "logout button" // First request arrives, second is on hold. First invalidates session and sends redirect as response, // but browser ignores and expects response in second request. But second request has longer a valid session. if (session == null) { try { response.sendRedirect(exitSessionWorkaround != null ? exitSessionWorkaround : ""); return; } catch (IOException e) { log.info("Session exit workaround failed to to IOException (triple click?)"); } } SessionManager.setSession(session); session.setServletRequest(req); session.setServletResponse(response); session.fireRequestEvent(SRequestEvent.REQUEST_START); // in case, the previous thread did not clean up. SForm.clearArmedComponents(); Device outputDevice = null; ReloadManager reloadManager = session.getReloadManager(); try { /* * The tomcat 3.x has a bug, in that it does not encode the URL * sometimes. It does so, when there is a cookie, containing some * tomcat sessionid but that is invalid (because, for instance, * we restarted the tomcat in-between). * [I can't think of this being the correct behaviour, so I assume * it is a bug. ] * * So we have to workaround this here: if we actually got the * session id from a cookie, but it is not valid, we don't do * the encodeURL() here: we just leave the requestURL as it is * in the properties .. and this is url-encoded, since * we had set it up in the very beginning of this session * with URL-encoding on (see WingServlet::newSession()). * * Vice versa: if the requestedSessionId is valid, then we can * do the encoding (which then does URL-encoding or not, depending * whether the servlet engine detected a cookie). * (hen) */ RequestURL requestURL = null; if (req.isRequestedSessionIdValid()) { requestURL = new RequestURL("", getSessionEncoding(response)); // this will fire an event, if the encoding has changed .. session.setProperty("request.url", requestURL); } if (log.isDebugEnabled()) { log.debug("Request URL: " + requestURL); log.debug("HTTP header:"); for (Enumeration en = req.getHeaderNames(); en.hasMoreElements();) { String header = (String) en.nextElement(); log.debug(" " + header + ": " + req.getHeader(header)); } } handleLocale(req); // The pathInfo addresses the resource String pathInfo = req.getPathInfo(); // Note: Websphere returns null here! if (pathInfo != null && pathInfo.length() > 0) { // strip of leading / pathInfo = pathInfo.substring(1); } if (log.isDebugEnabled()) log.debug("pathInfo: " + pathInfo); ResourceMapper mapper = session.getResourceMapper(); // The externalizer is able to handle static and dynamic resources ExternalizeManager extManager = session.getExternalizeManager(); ExternalizedResource extInfo; Resource resource; if (pathInfo == null || pathInfo.length() == 0) extInfo = extManager.getExternalizedResource(retrieveCurrentRootFrameResource().getId()); else if (mapper != null && (resource = mapper.mapResource(pathInfo)) != null) extInfo = extManager.getExternalizedResource(resource.getId()); else if (firstRequest) { extInfo = extManager.getExternalizedResource(retrieveCurrentRootFrameResource().getId()); } else extInfo = extManager.getExternalizedResource(pathInfo); firstRequest = false; // Special case handling: We request a .html resource of a session which is not accessible. // This happens some times and leads to a 404, though it should not be possible. if (extInfo == null && pathInfo != null && (pathInfo.endsWith(".html") || pathInfo.endsWith(".xml"))) { log.info("Got a request to an invalid .html during a valid session .. redirecting to root frame."); response.sendRedirect(""); return; } if (extInfo != null && extInfo.getObject() instanceof UpdateResource) { reloadManager.setUpdateMode(true); String eventEpoch = req.getParameter("event_epoch"); UpdateResource updateResource = (UpdateResource) extInfo.getObject(); updateResource.getFrame().getEventEpoch(); if (eventEpoch != null && !eventEpoch.equals(updateResource.getFrame().getEventEpoch())) { reloadManager.setUpdateMode(false); } } else { reloadManager.setUpdateMode(false); } // Prior to dispatching the actual events we have to detect // their epoch and inform the dispatcher which will then be // able to check if the request is valid and processed. If // this is not the case, we force a complete page reload. LowLevelEventDispatcher eventDispatcher = session.getDispatcher(); eventDispatcher.setEventEpoch(req.getParameter("event_epoch")); Enumeration en = req.getParameterNames(); final Cookie[] cookies = req.getCookies(); // handle debug.cookie - read it every time. session.removeProperty("debug.cookie"); final Collection cookiesToDispatch = new ArrayList<>(); if (cookies != null) { //handle cookies for (Cookie cookie : cookies) { String paramName = cookie.getName(); if ("DEBUG".equals(paramName)) { // Cookies have a limited length, therefore we copy // them trustingly into the session. // Use a Tokenizer for performance. String paramValue = URLDecoder.decode(cookie.getValue(), "ISO-8859-1"); StringTokenizer tokenizer = new StringTokenizer(paramValue, "|"); String[] values = new String[tokenizer.countTokens()]; for (int j = 0; j < values.length; j++) { values[j] = tokenizer.nextToken(); } session.setProperty("debug.cookie", values); } else { cookiesToDispatch.add(cookie); } } } // are there parameters/low level events to dispatch if (en.hasMoreElements()) { // only fire DISPATCH_START if we have parameters to dispatch session.fireRequestEvent(SRequestEvent.DISPATCH_START); eventDispatcher.startLowLevelEventPhase(); if (cookiesToDispatch != null) { //dispatch cookies for (Cookie cookie : cookiesToDispatch) { String paramName = cookie.getName(); String value = cookie.getValue(); if (log.isDebugEnabled()) log.debug("dispatching cookie " + paramName + " = " + value); eventDispatcher.dispatch(paramName, value); } } if (log.isDebugEnabled()) { log.debug("Parameters:"); for (Enumeration e = req.getParameterNames(); e.hasMoreElements();) { String paramName = (String) e.nextElement(); StringBuilder param = new StringBuilder(); param.append(" ").append(paramName).append(": "); final String[] values = req.getParameterValues(paramName); param.append(values != null ? Arrays.toString(values) : "null"); log.debug(param.toString()); } } while (en.hasMoreElements()) { String paramName = (String) en.nextElement(); String[] values = req.getParameterValues(paramName); //We do not need to dispatch the event epoch since it is already // handled a few lines above. Furthermore we will not dispatch any // names that start with an '_' (e.g. _xhrId or parts of XCalendar). if (paramName.equals("event_epoch") || paramName.startsWith("_") || paramName.equals("comet") || paramName.equals("polling")) { continue; } String value = values[0]; // Split the values of the event trigger if (paramName.equals("event_trigger")) { int pos = value.indexOf('|'); paramName = value.substring(0, pos); values = new String[] { value.substring(pos +1) }; } // Handle form submit via default button if (paramName.equals("default_button")) { if (value.equals("undefined")) { continue; } else { paramName = values[0]; values = new String[] { "1" }; } } if (log.isDebugEnabled()) log.debug("dispatching " + paramName + " = " + Arrays.asList(values)); eventDispatcher.dispatch(paramName, values); } eventDispatcher.endLowLevelEventPhase(); SForm.fireEvents(); // only fire DISPATCH DONE if we have parameters to dispatch session.fireRequestEvent(SRequestEvent.DISPATCH_DONE); } session.fireRequestEvent(SRequestEvent.PROCESS_REQUEST); eventDispatcher.invokeRunnables(); // if the user chose to exit the session as a reaction on an // event, we got an URL to redirect after the session. /* * where is the right place? * The right place is * - _after_ we processed the events * (e.g. the 'Pressed Exit-Button'-event or gave * the user the chance to exit this session in the custom * processRequest()) * - but _before_ the rendering of the page, * because otherwise an redirect won't work, since we must * not have sent anything to the output stream). */ if (session.getExitAddress() != null) { try { session.firePrepareExit(); session.fireRequestEvent(SRequestEvent.REQUEST_END); String redirectAddress; if (session.getExitAddress().length() > 0) { // redirect to user requested URL. redirectAddress = session.getExitAddress(); } else { // redirect to a fresh session. redirectAddress = req.getRequestURL().toString(); if (pathInfo != null) { // Websphere pathinfo is null // Make sure that the redirect address doesn't contain any path info. redirectAddress = redirectAddress.substring(0, redirectAddress.length() - pathInfo.length()); } } exitSessionWorkaround = redirectAddress; if (reloadManager.isUpdateMode()) { ScriptListener listener = new JavaScriptListener(null, null, "location.href='" + redirectAddress + '\''); ScriptManager.getInstance().addScriptListener(listener); req.getSession().invalidate(); // calls destroy implicitly } else { response.sendRedirect(redirectAddress); req.getSession().invalidate(); // calls destroy implicitly return; } } catch (ExitVetoException ex) { session.exit(null); } // end of try-catch } if (session.getRedirectAddress() != null) { handleRedirect(response); return; } reloadManager.invalidateFrames(); if (extInfo != null) { outputDevice = DeviceFactory.createDevice(extInfo); try { session.fireRequestEvent(SRequestEvent.DELIVER_START, extInfo); long startTime = System.currentTimeMillis(); extManager.deliver(extInfo, response, outputDevice); if (log.isDebugEnabled()) { log.debug("Rendering time: " + (System.currentTimeMillis() - startTime) + " ms"); } } finally { session.fireRequestEvent(SRequestEvent.DELIVER_DONE, extInfo); } } else { handleUnknownResourceRequested(req, response); } } catch (Throwable e) { log.error("Uncaught Exception", e); handleException(response, e); } finally { if (session != null) { session.fireRequestEvent(SRequestEvent.REQUEST_END); } if (outputDevice != null) { try { outputDevice.close(); } catch (Exception e) { } } /* * the session might be null due to destroy(). */ if (session != null) { reloadManager.clear(); session.setServletRequest(null); session.setServletResponse(null); } // make sure that the session association to the thread is removed // from the SessionManager SessionManager.removeSession(); SForm.clearArmedComponents(); } } /** * Searches the current session for the root HTML frame and returns the Resource * representing this root HTML frame (i.e. for you to retrieve the externalizer id * via getId()-method). * @return Resource of the root HTML frame */ private Resource retrieveCurrentRootFrameResource() throws ServletException { log.debug("delivering default frame"); if (session.getFrames().size() == 0) throw new ServletException("no frame visible"); // get the first frame from the set and walk up the hierarchy. // this should work in most cases. if there are more than one // toplevel frames, the developer has to care about the resource // ids anyway .. SFrame defaultFrame = (SFrame) session.getFrames().iterator().next(); while (defaultFrame.getParent() != null) defaultFrame = (SFrame) defaultFrame.getParent(); return defaultFrame.getDynamicResource(ReloadResource.class); } private void handleRedirect(HttpServletResponse response) { try { ReloadManager reloadManager = session.getReloadManager(); if (reloadManager.isUpdateMode()) { String script = "wingS.request.sendRedirect(\"" + session.getRedirectAddress() + "\");"; session.getScriptManager().addScriptListener(new JavaScriptListener(null, null, script)); /* Resource root = retrieveCurrentRootFrameResource(); ExternalizedResource externalizedResource = session.getExternalizeManager().getExternalizedResource(root.getId()); session.fireRequestEvent(SRequestEvent.DELIVER_START, externalizedResource); String encoding = session.getCharacterEncoding(); response.setContentType("text/xml; charset=" + encoding); ServletOutputStream out = response.getOutputStream(); Device outputDevice = new ServletDevice(out); UpdateResource.writeHeader(outputDevice); UpdateResource.writeUpdate(outputDevice, "wingS.request.sendRedirect(\"" + session.getRedirectAddress() + "\");"); UpdateResource.writeFooter(outputDevice); outputDevice.flush(); session.fireRequestEvent(SRequestEvent.DELIVER_DONE, externalizedResource); session.fireRequestEvent(SRequestEvent.REQUEST_END); reloadManager.clear(); session.setServletRequest(null); session.setServletResponse(null); SessionManager.removeSession(); SForm.clearArmedComponents(); */ } else { response.sendRedirect(session.getRedirectAddress()); } } catch (Exception e) { log.warn(e.getMessage(), e); } finally { session.setRedirectAddress(null); } } /** * In case of an error, display an error page to the user. This is only * done if there is a properties wings.error.handler defined * in the web.xml file. If the property is present, the following steps * are performed: *
  • Load the class named by the value of that property, using the * current thread's context class loader, *
  • Instantiate that class using its zero-argument constructor, *
  • Cast the instance to ExceptionHandler, *
  • Invoke the handler's handle method, passing it the * thrown argument that was passed to this method. * * * @see DefaultExceptionHandler * @param response the HTTP Response to use * @param thrown the Exception to report */ protected void handleException(HttpServletResponse response, Throwable thrown) { try { String className = (String)session.getProperty("wings.error.handler"); if (className == null) className = DefaultExceptionHandler.class.getName(); ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Class clazz = classLoader.loadClass(className); ExceptionHandler exceptionHandler = (ExceptionHandler)clazz.newInstance(); StringBuilderDevice device = new StringBuilderDevice(4096); exceptionHandler.handle(device, thrown); Resource resource = new StringResource(device.toString(), "html", "text/html"); String url = session.getExternalizeManager().externalize(resource); ReloadManager reloadManager = session.getReloadManager(); session.fireRequestEvent(SRequestEvent.DISPATCH_DONE); session.fireRequestEvent(SRequestEvent.PROCESS_REQUEST); if (reloadManager.isUpdateMode()) { Resource root = retrieveCurrentRootFrameResource(); ExternalizedResource externalizedResource = session.getExternalizeManager().getExternalizedResource(root.getId()); session.fireRequestEvent(SRequestEvent.DELIVER_START, externalizedResource); String encoding = session.getCharacterEncoding(); response.setContentType("text/xml; charset=" + encoding); ServletOutputStream out = response.getOutputStream(); Device outputDevice = new ServletDevice(out, encoding); UpdateResource.writeHeader(outputDevice); UpdateResource.writeUpdate(outputDevice, "wingS.request.sendRedirect(\"" + url + "\");"); UpdateResource.writeFooter(outputDevice); outputDevice.flush(); session.fireRequestEvent(SRequestEvent.DELIVER_DONE, externalizedResource); session.fireRequestEvent(SRequestEvent.REQUEST_END); reloadManager.clear(); session.setServletRequest(null); session.setServletResponse(null); SessionManager.removeSession(); SForm.clearArmedComponents(); } else { // TODO FIXME: This redirect is in most times too late. Redirect works only if no byte // has yet been sent to the client (dispatch phase) // Won't work if exception occurred during rendering phase // Solution: Provide / send javascript code to redirect. response.sendRedirect(url); } } catch (Exception e) { log.warn(e.getMessage(), thrown); } } /** * This method is called, whenever a Resource is requested whose * name is not known within this session. * * @param req the causing HttpServletRequest * @param res the HttpServletResponse of this request */ protected static void handleUnknownResourceRequested(HttpServletRequest req, HttpServletResponse res) throws IOException { // res.setStatus(HttpServletResponse.SC_NOT_FOUND); // res.setContentType("text/html"); // res.getOutputStream().println("

    404 Not Found

    Unknown Resource Requested: " + req.getPathInfo()); res.reset(); res.sendError(HttpServletResponse.SC_NOT_FOUND, req.getPathInfo()); } /* HttpSessionBindingListener */ @Override public void valueBound(HttpSessionBindingEvent event) { } /* HttpSessionBindingListener */ @Override public void valueUnbound(HttpSessionBindingEvent event) { destroy(); } /** * get the Session Encoding, that is appended to each URL. * Basically, this is response.encodeURL(""), but unfortuntatly, this * empty encoding isn't supported by Tomcat 4.x anymore. */ public static String getSessionEncoding(HttpServletResponse response) { if (response == null) return ""; // encode dummy non-empty URL. return response.encodeURL("foo").substring(3); } /** * Destroy and cleanup the session servlet. */ @Override public void destroy() { log.info("destroy called"); if (session != null) { // Session is needed on destroying the session SessionManager.setSession(session); session.destroy(); } } /** * A check if this session servlet seems to be alive or is i.e. invalid because it * was deserialized. * * @return true, if this session servlet seems to be valid and alive. */ public boolean isValid() { return session != null && parent != null; } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy