A recipe to integrate Lift in an existing Spring-based web application

This is a post that summarizes how you can integrate Liftweb in an existing Spring-based web application.
The description is based on an actual example project of which the code is available at the Scala Spring project at Google code.

Before we start with the recipe, first some notes about the assumptions for this integration:

  • This recipe assumes Lift 1.0.2 and is about putting Lift’s webkit module to use (Lift also has modules for data access for example, but we will still use Spring for data access).
  • It also assumes that all Spring objects that the Lift-based code needs to access are available in the root Spring ApplicationContext. So in the example project they are available in applicationContext-jdbc.xml as opposed to the servlet specific ApplicationContext petclinic-servlet.xml.

Ok, the recipe:

  1. Specify the necessary dependencies.
            <properties>
                    <scala.version>2.7.5</scala.version>
                    <lift.version>1.0.2</lift.version>
            </properties>
    
            <dependencies>
                    <dependency>
                            <groupId>org.scala-lang</groupId>
                            <artifactId>scala-compiler</artifactId>
                            <version>${scala.version}</version>
                    </dependency>
                    <dependency>
                            <groupId>org.scala-lang</groupId>
                            <artifactId>scala-library</artifactId>
                            <version>${scala.version}</version>
                    </dependency>
                    <dependency>
                            <groupId>net.liftweb</groupId>
                            <artifactId>lift-webkit</artifactId>
                            <version>${lift.version}</version>
                    </dependency>
                    <dependency>
                            <groupId>net.liftweb</groupId>
                            <artifactId>lift-util</artifactId>
                            <version>${lift.version}</version>
                    </dependency>
            </dependencies>
    

    See: pom.xml

  2. Add a filter and filter-mapping to the web.xml. For example:
    	<filter>
    		<filter-name>LiftFilter</filter-name>
    		<filter-class>net.liftweb.http.LiftFilter</filter-class>
    	</filter>
    
    	<filter-mapping>
    		<filter-name>LiftFilter</filter-name>
    		<url-pattern>lift/*</url-pattern>
    	</filter-mapping>
    

    See: web.xml

  3. In Lift’s Boot class configure where Lift should look for snippets. For example:
    LiftRules.addToPackages("org.springframework.samples.petclinic.lift")

    See Boot.scala

  4. Create a Lift template
    <lift:surround with="default" at="content">
      <head><title>Owner</title></head>
    
      <lift:OwnerSnippet.add form="post">
      <h2><owner:newText/>Owner:</h2>
      <table>
        <owner:firstName />
        <owner:lastName />
        <owner:address />
        <owner:city />
        <owner:telephone />
        <owner:submit />
      </table>
      </lift:OwnerSnippet.add>
    </lift:surround>
    

    See ownerForm.html. This particular template uses a default template via the lift:surround element to wrap it in some standard html, but you can organize it any way you like.

  5. And finally a snippet:
    class OwnerSnippet {
    
      // Set up a requestVar to track the object for edits and adds
      object ownerVar extends RequestVar(new Owner())
      def owner = ownerVar.is
    
      def add (xhtml : NodeSeq) : NodeSeq = {
    
        def doAdd () = {
          // Reusing the spring validator code
          val result = new MapBindingResult(new HashMap(), "owner")
          new OwnerValidator().validate(owner, result)
          LiftUtils.toLiftErrors(result)
          if (!result.hasErrors()) {
            try {
              LiftUtils.clinic.storeOwner(owner)
              S.redirectTo("/owner.do?ownerId=" + owner.id)
            } catch {
              case e : DataAccessException => error("DataAccessException")
            }
          }
        }
    
        // Hold a val here so that the "id" closure holds it when we re-enter this method
        val currentId = owner.id
    
        val submitText = if (owner.isNew) "Add Owner" else "Update Owner"
    
        bind("owner", xhtml,
         "newText" -> (if (owner.isNew) "New " else ""),
         "id" -> SHtml.hidden(() => owner.id = currentId),
         "firstName" -> LiftUtils.field("First Name: ", owner.firstName, owner.firstName = _, "size" -> "30", "maxlength" -> "80", "id" -> "firstName"),
         "lastName" -> LiftUtils.field("Last Name: ", owner.lastName, owner.lastName = _, "size" -> "30", "maxlength" -> "80", "id" -> "lastName"),
         "address" -> LiftUtils.field("Address: ", owner.address, owner.address = _, "size" -> "30", "maxlength" -> "80", "id" -> "address"),
         "city" -> LiftUtils.field("City: ", owner.city, owner.city = _, "size" -> "30", "maxlength" -> "80", "id" -> "city"),
         "telephone" -> LiftUtils.field("Telephone:", owner.telephone, owner.telephone = _, "size" -> "20", "maxlength" -> "20", "id" -> "telephone"),
         "submit" -> <tr><td><p class="submit"> { SHtml.submit(submitText, doAdd) } </p></td></tr>
        )
      }
    
    }
    

Note that in this example we are reusing the validation code that was originally written for Spring (line 10-14).

Also note that it is up to you whether you define your code as a template or snippet. E.g. it would be easy to translate the ownerForm template into a snippet, or vice versa to move more code into the template.

Furthermore, check out the nifty way how Lift enables you to write the add method. What I especially like is the way the type safe way the owner is constructed (lines 4 and 5), how binding works (lines 29 and further ) and the way Scala and Lift allow you to implement a form submit (see line 37 for the call to doAdd and the method definition of doAdd starting at line 9).

On the other side, a Spring controller allows you your dependencies to be injected whereas in Lift you need to call out to obtain the objects you require. For example, in the Spring controller AddOwnerForm.scala we have a setClinic method that injects a Clinic. On the other side, in the Lift snippet we need to call out to the ApplicationContext via the ServletContext to obtain the Clinic object (see line 16 LiftUtils.clinic and it’s implementation).

By the way, there has been an interesting discussion about dependency Injection in Lift. Read that and decide what your opinion is.

©2006 - 2010 Rintcius Blok