Warp :: Servlet Guide

Managed Servlets & Filters with Guice and warp-servlet 

Enabling the WebFilter

Enabling warp-servlet in your applications is done similarly to guice-servlet. First you need to register a filter in web.xml:

	<filter>
		<filter-name>warpServletFilter</filter-name>
		<filter-class>com.wideplay.warp.servlet.WebFilter</filter-class> 
	</filter> 
	<filter-mapping>
		<filter-name>warpServletFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping> 
 

This filter is somewhat analogous to Spring and Acegi's FilterToBeanProxy, if you know what that does. Now, you need to tell warp-servlet about your injector. This is done exactly as in guice-servlet (or Dwr) via the use of a ServletContextListener. Subclass and register the WarpServletContextListener as follows:

public class MyInjectorCreator extends WarpServletContextListener {
 
	@Override
	protected Injector getInjector() {
		return Guice.createInjector(..., Servlets.configure()
			.filters()
			.buildModule(); 
 

Then register your newly minted listener in web.xml:

<listener>
	<listener-class>my.domain.MyInjectorCreator</listener-class> 
</listener>

Simple! Now you are ready to rock and roll with warp-servlet.

Configuring a Managed Servlet

OK, now that you have everything set up, you'll want to register a servlet to run via warp-servlet and benefit from the wonders of Guice injection. Let's say you have a servlet called MyServlet, in your injector configuration add the following mapping:

	Guice.createInjector(..., Servlets.configure()
		.filters()
 
		.servlets()
			.serve("*.html").with(MyServlet.class)
 
 		.buildModule();
 

This is equivalent to a uri-mapping of *.html with MyServlet as the servlet class (note that MyServlet needs to extend HttpServlet). That's it! You don't have to add anything more to web.xml, simply deploy the webapp in your favorite container and MyServlet will get called whenever an html file is requested. 

If you want to map .ajax URIs to MyAjaxServlet using a regular expression, try the following:

		.servlets()
			.serve("*.html").with(MyServlet.class)
			.serveRegex("(.)*\\.ajax").with(MyAjaxServlet.class
 

If two servlets map to the same incoming URI, then the first one registered is always preferred and any others are ignored. Also, once a servlet registered with warp-servlet has serviced a request, it will *not* be dispatched to the regular servlet pipeline. If no mapping is found, warp-servlet continues the request down the servlet container's pipeline (this way you can serve static resources with the default servlet or JSPs with the bundled JSP servlet, unhindered).

Configuring Managed Filters

Configuring a Filter is done in much the same way as a servlet. The order of registration is important and follows the same rules as in web.xml:

		.filters()
			.filter("/*").through(MyFilter.class)
			.filterRegex("(.)*").through(MyFilter.class) 

Naturally, MyFilter must implement javax.servlet.Filter. Filters that do not invoke the provided FilterChain in their doFilter() methods will suppress the rest of the pipeline (exactly how it works in web.xml) otherwise they will continue down normally. Filters matching the same URIs are resolved and dispatched in the order in which they are registered (filters that don't match URIs are skipped).

Note: Apache Wicket cannot be configured this way. See this guide for an alternative.

Passing in Init Parameters

Some Servlets and Filters require externally-set String parameters, these are generally passed in via the nested <init-param> tag in web.xml. In warp-servlet you pass in a Map of name-value pairs that are adapted and fed to the Servlet or Filter as init parameters in their normal configuration:

	Map<String, String> params = new HashMap<String, String>();
	params.put("myParameterName", "myParameterValue"); 
	//...
 
	//and in your injector configuration 
	.filters()
		.filter("/*").through(MyFilter.class, params)
 

Since Properties exposes the Map interface, you can externalize your init parameters to a ResourceBundle (a .properties file), which is a neat saving. Servlet mappings support parameters in the same fashion, I'll leave it to you to work them out.

Init parameters configured via the <init-param> tag for WebFilter in web.xml do not get passed on to filters or servlets registered to warp-servlet.

Context parameters configured via the <context-param> tag in web.xml are available to filters or servlets registered to warp-servlet in the normal fashion.

Configuring Servlets and Filters by Key

One of the neat things about Guice is that it allows you to hide all your implementation classes as package-local and simply bind them to public interfaces. Typically, one uses binding annotations to distinguish between different implementations of the same interface. Warp-serlvet lets you do this with Servlets and Filters too:

 	.filters()
.filter("/*").through(Key.get(Filter.class, MyAnnotation.class))
 
	.servlets()
		.serve("*.html).with(Key.get(HttpServlet.class, Names.named("myServlet!"))) 
 

And in your Guice module, make sure you bind concrete implementations (or providers) appropriately:

	protected void configure() {
 		bind(Filter.class).annotatedWith(MyAnnotation.class)
			.to(MyFilter.class);
	}

Setting Scopes on Servlets and Filters (important!)

Warp-servlet fetches servlets and filters from the Guice injector on every request. This gives you the opportunity to bind them into a scope. However, this can be dangerous if you don't give it careful thought first. Here are a couple of things to keep in mind:

  • If you do not explicitly specify a scope, servlets and filters will be instantiated on every dispatch (i.e. bound under the default "no-scope")
  • Generally, you will want Filters and Servlets to be bound as singletons
  • Servlet lifecycle events (init & destroy) are only fired once, when the servlet container fires them. If your servlets are not bound as singletons, they will not receive an init event every time they are instantiated
  • Declaring request and session scope on a registered servlet or filter is dangerous because warp-servlet will attempt to obtain them to fire lifecycle events (outside a request) raising an OutOfScopeException

If you really want your servlets to have request or session scope, then create wrapping servlets that delegate only their service() or doGet() and doPost() calls. Register these wrapping servlets as singletons with warp-servlet and simply inject your request or session scoped servlets into them via Providers. Call get() and delegate to them on every request dispatch. Likewise with filters.

Other things to keep in mind

  • Use Servlets.REQUEST_SCOPE and Servlets.SESSION_SCOPE to bind your objects into those scopes respectively.
  • @RequestScoped and @SessionScoped annotations are provided but not bound by default (you can easily bind them with a call to bindScope() or you can directly use the scopes as above). The reason for this is that if you want to use guice-servlet's annotations for whatever reason you can still do so; however make sure you don't use the guice-servlet scopes themselves!
  • Additionally Servlets.FLASH_SCOPE (and @FlashScoped) is available. Flash Scope is a scope that lives between two consecutive requests in a session and is useful with the post-and-redirect design pattern. 
  • You can obtain the current set of request parameters (inside a request) by injecting a Map<String, String[]> annotated with @RequestParameters, just like in guice-servlet.
  • You can obtain the current request and response by injecting HttpServletRequest or HttpServletResponse in any of your guice objects.