How to replace XML servlet mappings with Scalatra mounts

This is an excerpt from the Scala Cookbook (partially modified for the internet). This is a short recipe, Recipe 15.6, “How to replace XML servlet mappings with Scalatra mounts.”

Problem

You want to add new servlets to your Scalatra application, and need to know how to add them, including defining their URI namespace.

Solution

Scalatra provides a nice way of getting you out of the business of declaring your servlets and servlet mappings in the web.xml file. Simply create a boilerplate web.xml file like this in the src/main/webapp/WEB-INF directory:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee "http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
      version="3.0">
    <listener>
      <listener-class>org.scalatra.servlet.ScalatraListener</listener-class>
    </listener>
    <servlet-mapping>
      <servlet-name>default</servlet-name>
      <url-pattern>/img/*</url-pattern>
      <url-pattern>/css/*</url-pattern>
      <url-pattern>/js/*</url-pattern>
      <url-pattern>/assets/*</url-pattern>
    </servlet-mapping>
</web-app>

Next, assuming that you’re working with the application created in Recipe 15.5, edit the src/main/scala/ScalatraBootstrap.scala file so that it has these contents:

import org.scalatra._
import javax.servlet.ServletContext
import com.alvinalexander.app._

class ScalatraBootstrap extends LifeCycle {
    override def init(context: ServletContext) {

        // created by default
        context.mount(new MyScalatraServlet, "/*")

        // new
        context.mount(new StockServlet, "/stocks/*")
        context.mount(new BondServlet, "/bonds/*")
    }
}

The two new context.mount lines shown tell Scalatra that a class named StockServlet should handle all URI requests that begin with /stocks/, and another class named BondServlet should handle all URI requests that begin with /bonds/.

Next, create a file named src/main/scala/com/alvinalexander/app/OtherServlets.scala to define the StockServlet and BondServlet classes:

package com.alvinalexander.app

import org.scalatra._
import scalate.ScalateSupport

class StockServlet extends MyScalatraWebAppStack {
    get("/") {
        <p>Hello from StockServlet</p>
    }
}

class BondServlet extends MyScalatraWebAppStack {
    get("/") {
        <p>Hello from BondServlet</p>
    }
}

Assuming your project is still configured to recompile automatically, when you access the http://localhost:8080/stocks/ and http://localhost:8080/bonds/ URLs, you should see the content from your new servlets.

Discussion

Scalatra refers to this configuration process as “mounting” the servlets, and if you’ve used a filesystem technology like NFS, it does indeed feel similar to the process of mounting a remote filesystem.

As a result of the configuration, new methods in the StockServlet and BondServlet will be available under the /stocks/ and /bonds/ URIs. For example, if you define a new method like this in the StockServlet:

get("/foo") {
    <p>Foo!</p>
}

you’ll be able to access this method at the /stocks/foo URI, e.g., the http://localhost:8080/stocks/foo URL, if you’re running on port 8080 on your local computer.

In the end, this approach provides the same functionality as servlet mappings, but it’s more concise, with the added benefit that you’re working in Scala code instead of XML, and you can generally forget about the web.xml file after the initial configuration.

See Also