8 Under the hood of the request processing - Reference Documentation
Authors: Andrea Del Bene, Carsten Hufe, Christian Kroemer, Daniel Bartl
Version: 1.0.0.BUILD-SNAPSHOT
Table of Contents
8 Under the hood of the request processing
Although Wicket was born to provide a reliable and comprehensive object oriented abstraction for web development, sometimes we might need to work directly with “raw” web entities such as user session, web request, query parameters, and so on. For example this is necessary if we want to store an arbitrary parameter in the user session.Wicket provides wrapper classes that allow us to easily access to web entities without the burden of using the low-level APIs of Java Servlet Specification. However it will always be possible to access standard classes (like HttpSession, HttpServletRequest, etc...) that lay under our Wicket application. This chapter will introduce these wrapper classes and it will explain how Wicket uses them to handle the web requests initiated by the user's browser.8.1 Class Application and request processing
Beside configuring and initializing our application, the Application class is responsible for creating the internal entities used by Wicket to process a request. These entities are instances of the following classes: RequestCycle, Request, Response and Session.The next paragraphs will illustrate each of these classes, explaining how they are involved into request processing.8.2 Request and Response classes
The Request and Response classes are located in package org.apache.wicket.request and they provide an abstraction of the concrete request and response used by our web application.Both classes are declared as abstract but if our application class inherits from WebApplication it will use their sub classes ServletWebRequest and ServletWebResponse, both of them located inside the package org.apache.wicket.protocol.http.servlet.ServletWebRequest and ServletWebResponse wrap respectively a HttpServletRequest and a HttpServletResponse object. If we need to access to these low-level objects we can call Request's method getContainerRequest() and Response's method getContainer Response().8.3 The “director” of request processing - RequestCycle
Class org.apache.wicket.request.cycle.RequestCycle is the entity in charge of serving a web request. Our application class creates a new RequestCycle on every request with its method createRequestCycle(request, response).Method createRequestCycle is declared as final, so we can't override it to return a custom subclass of RequestCycle. Instead, we must build a request cycle provider implementing interface org.apache.wicket.IRequestCycleProvider, and then we must tell our application class to use it via the setRequestCycleProvider method.The current running request cycle can be retrieved at any time by calling its static method RequestCycle.get(). Strictly speaking this method returns the request cycle associated with the current (or local) thread, which is the thread that is serving the current request. A similar get() method is also implemented in classes org.apache.wicket.Application (as we have seen in paragraph 2.2.2) and org.apache.wicket.Session in order to get the application and the session in use by the current thread.The implementation of the get method takes advantage of the standard class java.lang.ThreadLocal. See its JavaDoc for an introduction to local-thread variables.Class org.apache.wicket.Component provides the getRequestCycle() method which is a convenience method that internally invokes RequestCycle.get():
public final RequestCycle getRequestCycle() { return RequestCycle.get(); }
RequestCycle and request processing
This paragraph will provide just the basic informations about what happens behind the scenes of request processing. When you work with Wicket it's unlikely to have a need for customizing this process, so we won't cover this topic in detail.In order to process a request, RequestCycle delegates the task to another entity which implements interface org.apache.wicket.request.IRequestHandler. There are different implementations of this interface, each suited for a particular type of requested resource (a page to render, an AJAX request, an URL to an external page, etc.).To resolve the right handler for a given HTTP request, the RequestCycle uses a set of objects implementing the org.apache.wicket.request.IRequestMapper interface. The mapping interface defines the getCompatibilityScore(Request request) method which returns a score indicating how compatible the request mapper is for the current request. RequestCycle will choose the mapper with the highest score and it will call its mapRequest(Request request) method to get the proper handler for the given request. Once RequestCycle has resolved a request handler, it invokes its method respond(IRequestCycle requestCycle) to start request processing.The following sequence diagram recaps how a request handler is resolved by the RequestCycle:Developers can create additional implementations of IRequestMapper and add them to their application via the mount(IRequestMapper mapper) method of the WebApplication class. In paragraph 8.6 we will see how Wicket uses this method to add built-in mappers for mounted pages.
Generating URL with the urlFor and mapUrlFor methods
The RequestCycle is also responsible for generating the URL value (as CharSequence) for the following entities:- a page class, via the urlFor(Class<C> pageClass, PageParameters parameters) method
- an IRequestHandler via the urlFor(IRequestHandler handler) method
- a ResourceReference via the urlFor(ResourceReference reference, PageParameters params) method (resource entities will be introduced in chapter 13).
Method setResponsePage
The RequestCycle class contains the implementation of the setResponsePage method we use to redirect a user to a specific page (see paragraph 2.4). The namesake method of class org.apache.wicket. Component is just a convenience method that internally invokes the actual implementation on current request cycle:public final void setResponsePage(final Page page) { getRequestCycle().setResponsePage(page); }
RequestCycle's hook methods and listeners
The RequestCycle comes with some hook methods which can be overridden to perform custom actions when request handling reaches a specific stage. These methods are:- onBeginRequest(): called when the RequestCycle is about to start handling the request.
- onEndRequest(): called when the RequestCycle has finished to handle the request
- onDetach(): called after the request handling has completed and the RequestCycle is about to be detached from its thread. The default implementation of this method invokes detach() on the current session (the Session class will be shortly discussed in paragraph 7.4).
- onBeginRequest(RequestCycle cycle): (see the description above)
- onEndRequest(RequestCycle cycle): (see the description above)
- onDetach(RequestCycle cycle): (see the description above)
- onRequestHandlerResolved(RequestCycle cycle, IRequestHandler handler): called when an IRequestHandler has been resolved.
- onRequestHandlerScheduled(RequestCycle cycle, IRequestHandler handler): called when an IRequestHandler has been scheduled for execution.
- onRequestHandlerExecuted(RequestCycle cycle, IRequestHandler handler): called when an IRequestHandler has been executed.
- onException(RequestCycle cycle, Exception ex): called when an exception has been thrown during request processing.
- onExceptionRequestHandlerResolved(RequestCycle rc, IRequestHandler rh, Exception ex): called when an IRequestHandler has been resolved and will be used to handle an exception.
- onUrlMapped(RequestCycle cycle, IRequestHandler handler, Url url): called when an URL has been generated for an IRequestHandler object.
@Override public void init() { super.init(); IRequestCycleListener myListener; //listener initialization… getRequestCycleListeners().add(myListener) }
8.4 Session Class
In Wicket we use class org.apache.wicket.Session to handle session-relative informations such as client informations, session attributes, session-level cache (seen in paragraph 6.2.4), etc...In addition, we know from paragraph 6.1 that Wicket creates a user session to store versions of stateful pages. Similarly to what happens with RequestCycle, the new Session's instances are generated by the Application class with the newSession(Request request, Response response) method. This method is not declared as final, hence it can be overridden if we need to use a custom implementation of the Session class.By default if our custom application class is a subclass of WebApplication, method newSession will return an instance of class org.apache.wicket.protocol.http.WebSession. As we have mentioned talking about RequestCycle, also class Session provides a static get() method which returns the session associated to the current thread.Session and listeners
Similar to the RequestCycle, class org.apache.wicket.Session also offers support for listener entities. With Session these entities must implement the callback interface org.apache.wicket. ISessionListener which exposes only the onCreated(Session session) method. As you might guess from its name, this method is called when a new session is created. Session listeners must be added to our application using a typed collection, just like we have done before with request cycle listeners:@Override public void init(){ super.init(); //listener initialization… ISessionListener myListener; //add a custom session listener getSessionListeners().add(myListener)}
Handling session attributes
The Session class handles session attributes in much the same way as the standard interface javax.servlet.http.HttpSession. The following methods are provided to create, read and remove session attributes:- setAttribute(String name, Serializable value): creates an attribute identified by the given name. If the session already contains an attribute with the same name, the new value will replace the existing one. The value must be a serializable object.
- getAttribute(String name): returns the value of the attribute identified by the given name, or null if the name does not correspond to any attribute.
- removeAttribute(String name): removes the attribute identified by the given name.
Accessing to the HTTP session
If for any reason we need to directly access to the underlying HttpSession object, we can retrieve it from the current request with the following code:HttpSession session = ((ServletWebRequest)RequestCycle.get().getRequest()).getContainerRequest().getSession();
HttpSession session = ((ServletWebRequest)RequestCycle.get().getRequest()).getContainerRequest().getSession();session.setAttribute("Locale", "ENGLISH"); session.setAttribute("userName", "Mr BadGuy");
Temporary and permanent sessions
Wicket doesn't need to store data into user session as long as the user visits only stateless pages. Nonetheless, even under these conditions, a temporary session object is created to process each request but it is discarded at the end of the current request. To know if the current session is temporary, we can use the isTemporary() method:Session.get().isTemporary();
public class HomePage extends WebPage { public HomePage(final PageParameters parameters) { Session.get().setAttribute("username", "tommy"); Session.get().bind(); setResponsePage(DisplaySessionParameter.class); } }
public class DisplaySessionParameter extends WebPage { public DisplaySessionParameter() { super(); add(new Label("username", (String) Session.get().getAttribute("username"))); } }
Discarding session data
Once a user has finished using our web application, she must be able to log out and clean any session data. To be sure that a permanent session will be discarded at the end of the current request, class Session provides the invalidate() method. If we want to immediately invalidate a given session without waiting for the current request to complete, we can invoke the invalidateNow() method.Remember that invalidateNow() will immediately remove any instance of components (and pages) from the session, meaning that once we have called this method we won't be able to work with them for the rest of the request process.
Storing arbitrary objects with metadata
JavaServer Pages Specification1 defines 4 scopes in which a page can create and access a variable. These scopes are:- request: variables declared in this scope can be seen only by pages processing the same request. The lifespan of these variables is (at most) equal to the one of the related request. They are discarded when the full response has been generated or when the request is forwarded somewhere else.
- page: variables declared in this scope can be seen only by the page that has created them.
- session: variables in session scope can be created and accessed by every page used in the same session where they are defined.
- application: this is the widest scope. Variables declared in this scope can be used by any page of a given web application.
public static MetaDataApp extends WebApplication{ //Do some stuff… /** * Metadata key definition */ public static MetaDataKey<Connection> connectionKey = new MetaDataKey<Connection> (){}; /** * Application's initialization */ @Override public void init(){ super.init(); Connection connection; //connection initialization… setMetaData(connectionKey, connection); //Do some other stuff.. } }
Connection connection = Application.get().getMetaData(MetaDataApp.connectionKey);