Announcing Lift 2.0

Announcing Lift 2.0

June 30th, 2010

The Lift team and the Lift community are proud to announce the availability of Lift 2.0

"Lift is the kind of web framework that enables you as a developer to concentrate on the big picture. Strong, expressive typing and higher-level features like the built-in Comet support allow you to focus on innovating instead of the plumbing. Building a rich, real-time web application like Novell Pulse requires a framework with the power of Lift under the covers."
- David LaPalomento, Developer, Novell

"Foursquare switched over to Scala & Lift last September and we've been thrilled with the results. The ease of developing complex interactive ajax web pages enabled a very rapid port from our previous platform. In addition, the support we've gotten from David Pollak and the rest of the Lift team has been invaluable. It's clear they are very committed to making sure that production Lift deployments get all the attention they need to succeed."
- @harryh, Foursquare Engineering Tech Lead

Lift is an elegant, expressive web framework that allows any size team build and maintain secure, highly interactive, scalable web applicationsquickly and efficiently.  Lift is built on the Scala and compiles to JVM byte-code.  Lift applications deploy as WAR files on popular application servers and web cotainers including Jetty, Glassfish and Tomcat.  Lift applications can be monitored and managed with the same proven infrastructure used to manage and monitor any Java web application.  Lift is open source licensed under an Apache 2.0 license.

Lift features include:

  • Community... the Lift community is 2,000 members strong, super-active and always there to help with questions
  • Best Comet (server-push) support that allows the creation of dynamic application such as Novell Pulse
  • Super simple Ajax for creating highly interactive web applications without worrying about HTTP plumbing
  • Secure by default... Lift apps are resistant to the OWASP top 10 vulnerabilitys including XSS, XSRF, and parameter tampering
  • Concise and Maintainable... Lift apps typically contain fewer lines of code than corresponding Rails apps, yet are type safe so that many errors are flagged by the compiler
  • Scalable... Lift apps scale to millions of users across many servers, yet are highly efficient for single-box implementations
  • Compatible... Lift apps can take advantage of any Java library as well as the growing collection of Scala libraries

Lift 2.0's new features include:

  • NoSQL support including built-in support for MongoDB and CouchDB
  • High performance JSON support including an elegant JSON DSL and bidirectional JSON <-> Class conversion
  • Powerful, concise REST support
  • Support for enterprise infrastructure including JTA and LDAP
  • Declarative systems for single Screen input and validation as well as multiple screen Wizards
  • Radically improved development experience including much better error messages and support for dynamically changing system configuration
  • Support for running Lift apps outside of a J/EE Servlet containers
  • Improved Comet support including modern browser detection and better connection starvation detection
  • Improved support for testing including super-concise dependency injection and run-mode detection
  • Support for Simple Build Tool
  • Performance improvements

Lift-powered sites include:

  • Foursquare -- the multi-million user location based service that will soon surpass 1M+ checkins a day on their Lift-powered system
  • Novell Pulse -- enterprise collaboration software platform based on Google Wave
  • Xerox/XMPie -- the leading provider of software for cross-media, variable data one-to-one marketing
  • Snapsort -- Compare and decide on cameras
  • No Fouls -- Find pickup basketball games
Please join the Lift community and help use grow Lift.  And a super-big thanks to the 30+ Lift committers who have grown the Lift community and code-base to what it is today... and what it will be in the future!

Lift's Screen

Background

Much of the web is creating input forms for users to submit, validating those input forms and if the forms pass validation, an action is performed.  If the forms don't pass validation, the user is told which fields caused the validation problems and is given an opportunity to fix the problems.  Lift provides a single-screen input/validation mechanism called LiftScreen and a multi-page input/validation mechanism (with stateful next/previous buttons) called Wizard.  This post will discuss LiftScreen and the next post will discuss Wizard.

Both Wizard and Screen share the following attributes:
  • All logic can be tested without involving HTTP
  • All logic is declarative
  • All state is managed by Lift
  • The back-button works as the user would expect it to work
  • The form elements are strongly typed
  • The rendering logic and templates is divorced from the form logic
  • Basics

    First, let's declare a very simple input screen that asks your favorite ice cream flavor:

    object AskAboutIceCream1 extends LiftScreen {
      val flavor = field(S ? "What's your favorite Ice cream flavor", "")

      def finish() {
        S.notice("I like "+flavor.is+" too!")
      }
    }

    We create an object, a Scala singleton, called AskAboutIceCream1 which extends LiftScreen.  We declare a single field called flavor.  In our view, we refer to the LiftScreen with the following code:

    <lift:AskAboutIceCream1/>

    And we get a display:

      Cancel Finish

    When we submit the form, a notice is displayed agreeing with our ice cream choice.  But, we can enter a blank ice cream name and it will still be accepted.  That's not optimal.  We need to add some validation:

    object AskAboutIceCream2 extends LiftScreen {
      val flavor = field(S ? "What's your favorite Ice cream flavor", "",
                         trim, 
                         valMinLen(2, "Name too short"),
                         valMaxLen(40, "That's a long name"))

      def finish() {
        S.notice("I like "+flavor.is+" too!")
      }
    }

    This code trims the incoming string (removes any leading and trailing spaces) and then makes sure the length is reasonable.  So, if we enter a blank value, we get:

    •  
      • Name too short
      Cancel Finish

    We can add another field, this time a Boolean which turns into a checkbox:

    object AskAboutIceCream3 extends LiftScreen {
      val flavor = field(S ? "What's your favorite Ice cream flavor", "",
                         trim, valMinLen(2,S ? "Name too short"),
                         valMaxLen(40,S ? "That's a long name"))

      val sauce = field(S ? "Like chocalate sauce?", false)

      def finish() {
        if (sauce) {
          S.notice(flavor.is+" tastes especially good with chocolate sauce!")
        }
        else S.notice("I like "+flavor.is+" too!")
      }
    }

    And our display looks like:

      Cancel Finish
    The Boolean sauce field defaults to creating a checkbox rather than an text field.

    We can also do cross-field validation:

    object AskAboutIceCream4 extends LiftScreen {
      val flavor = field(S ? "What's your favorite Ice cream flavor", "",
                         trim, valMinLen(2,S ? "Name too short"),
                         valMaxLen(40,S ? "That's a long name"))

      val sauce = field(S ? "Like chocalate sauce?", false)

      override def validations = notTooMuchChocolate _ :: super.validations

      def notTooMuchChocolate(): Errors = {
        if (sauce && flavor.toLowerCase.contains("chocolate")) "That's a lot of chocolate"
        else Nil
      }

      def finish() {
        if (sauce) {
          S.notice(flavor.is+" tastes especially good with chocolate sauce!")
        }
        else S.notice("I like "+flavor.is+" too!")
      }
    }

    So, you you change the chocolate box and enter a flavor that contains chocolate, you get an error indicating that there's just too much chocolate.

    Working with Mapper and Record instances

    Turns out that LiftScreen works just ducky with Mapper and Record:

    object PersonScreen extends LiftScreen {
      object person extends ScreenVar(Person.create)

      override def screenTop =
      <b>A single screen with some input validation</b>

      _register(() => person.is)

    A Chatty kinda Lift Article

    A Chat Application in Lift

    by David Pollak and Steve Vinoski

    Last year, Debasish Ghosh and Steve Vinoski gave an overview of the Scala language, highlighting some of the features of Scala using the Lift Web framework in their article, “Scala and Lift—Functional Recipes for the Web.” We pick up where they left off in this column by taking a deeper dive into Lift, a Web framework in the vein of Seaside and WebObjects.

    http://www.computer.org/portal/web/computingnow/0610/whatsnew/internetcomputing

    Scala Option, Lift Box and how to make your code better

    Scala has a ton of nice features. One of the features that I was slow to adopt, until Burak Emir gently reminded me a bunch of times, is "Options". Read on about Options, Boxes, and how Lift makes good use of the to make clean, error resistant code.

    If you come from an imperative (Java, Ruby) background, you'll probably recognize the following code:

     

    x = someOperation
      if !x.nil?
      y = someOtherOperation
      if !y.nil? doSomething(x,y)
        return "it worked"
      end
    end
    return "it failed"

     

    Okay, so that's pseudo-code, but there are tons of operation, guard, operation, guard, blah blah constructs.

    Further, null/nil are passed around as failures. This is especially bad when it's null, but it's pretty bad when it's nil because it's not clear to the consumer of the API that there can be a "call failed" return value.

    In Java, null is a non-object. It has no methods. It is the exception to the statically typed rule (null has no class, but any reference of any class can be set to null.) Invoking a method on null has one and only one result: an exception is thrown. null is often returned from methods as a flag indicating that the method ran successfully, but yielded no meaningful value. For example, CardHolder.findByCreditCardNumber("2222222222")  In fact, the guy who invented null called it a billion dollar mistake.

    Ruby has nil which is marginally better than null. nil is a real, singleton object. There's only one instance of nil in the whole system. It has methods. It is a subclass of Object. Object has a method called "nil?" which returns false, except the nil singleton overrides this method to return true. nil is returned much like null in Java. It's the "no valid answer" answer.

    Scala does something different.

    There's an abstract class, called Option. Options are strongly typed. They are declared Option[T]. This means an Option can be of any type, but once its type is defined, it does not change. There are two subclasses of Option: Some and None. None is a singleton (like nil). Some is a container around the actual answer. So, you might have a method that looks like:

     

    def findUser(name: String): Option[User] = {
      val query = buildQuery(name)
      val resultSet = performQuery(query)
      val retVal = if (resultSet.next) Some(createUser(resultSet)) else None
      resultSet.close
      retVal
    }

     

     Some, you've got a findUser method that returns either Some(User) or None. So far, it doesn't look a lot different than our example above. So, to confuse everyone, I'm going to talk about collections for a minute.

    A really nice thing in Scala (yes, Ruby has this too) is rich list operations. Rather than creating a counter and pulling list (array) elements out one by one, you write a little function and pass that function to the list. The list calls the function with each element and returns a new list with the values returned from each call. It's easier to see it in code:

    scala> List(1,2,3).map(x => x * 2)
    line0: scala.List[scala.Int] = List(2,4,6)

    The above code multiplies each list item by two and "map" returns the resulting list. Oh, and you can be more terse, if you want:

    scala> List(1,2,3).map(_ * 2)
    line2: scala.List[scala.Int] = List(2,4,6)

    You can nest map operations:

    scala> List(1,2,3).map(x => List(4,5,6).map(y => x * y))
    line13: scala.List[scala.List[scala.Int]] = List(List(4,5,6),List(8,10,12),List(12,15,18))

    And, you can "flatten" the inner list:

    scala> List(1,2,3).flatMap(x => List(4,5,6).map(y => x * y))
    line14: scala.List[scala.Int] = List(4,5,6,8,10,12,12,15,18)

    Finally, you can "filter" only the even numbers from the first list:

    scala> List(1,2,3).filter(_ % 2 == 0).
           flatMap(x => List(4,5,6).map(y => x * y))
    line16: scala.List[scala.Int] = List(8,10,12)

    But, as you can see, the map/flatMap/filter stuff gets pretty verbose. Scala introduced a "for" comprehension to make the code more readable:

    scala> for {x <- List(1,2,3) if x % 2 == 0
                y <- List(4,5,6)} yield x * y
    res0: List[Int] = List(8, 10, 12)

     

    Okay, but what does this have to do with Option[T]?

    Turns out that Option implements map, flatMap, and filter (the methods necessary for the Scala compiler to use in the 'for' comprehension). Just as a side note, when I first encountered the phrase "'for' comprehension", I got scared. I've been doing programming for years and never heard of a "comprenhension" let alone a 'for' one. Turns out, that there's nothing fancy going on, but "'for' comprehension" is just a term of art for the above construct.

    So, the cool thing is that you can use this construct very effectively. The first example is simple:

    scala> for {x <- Some(3)
                y <- Some(4)} yield x * y
    res1: Option[Int] = Some(12)
     

    "That's nice, you just wrote a lot of code to multiply 3 by 4."

    Let's see what happens if we have a "None" in there:

    scala> val yOpt: Option[Int] = None
    yOpt: Option[Int] = None

    scala> for {x <- Some(3)
                y <- yOpt} yield x * y
    res3: Option[Int] = None
     

    So, we get a "None" back. How do we turn this into a default value?

    scala> (for {x <- Some(3); y <- yOpt} yield x * y) getOrElse -1
    res4: Int = -1


    scala> (for {x <- Some(3); y <- Some(4)} yield x * y) getOrElse -1
    res5: Int = 12
     

    Note that the "getOrElse" code is "passed by name". Put another way, that code is only executed if the "else" clause is valid.

    Lift has an analogous construct called Box.

    A Box Full or not.  A non-Full Box can be the Empty singleton or a Failure.  A Failure carries around information about why the Box contains no value.

    Failure is very helpful because you can carry around information to display an error... an HTTP response code, a message, what have you.

    In Lift, I put this all together in the following way:

    • methods that return request parameters return Box[String]
    • finder methods on models (not find all, just the ones that return a single instance) return Box[Model]
    • any method that would have returned a null if I was writing in Java returns a Box[T] in Lift

    That means you get code that looks like:

    scala> for {id <- S.param("id") ?~ "id param missing"
    u <- getUser(id) ?~ "User not found"
    } yield u.toXml
    res6: net.liftweb.common.Box[scala.xml.Elem] = Failure(id param missing,Empty,Empty)
     


    There's no explicit guard/test to see if the "id" parameter was passed in and there's no explicit test to see if the user was found.

    Note also that this code is completely type-safe. While there was no explicit type declarations, the compiler was able to figure out what types the various objects were.

    So, let's look at the code inside a REST handler:

      serve {
        case "user" :: "info" :: _ XmlGet _ =>
          for {
            id <- S.param("id") ?~ "id param missing" ~> 401
            u <- User.find(id) ?~ "User not found"
          } yield u.toXml
      }

    If the id parameter is missing, present a nice error message and return a 401 (okay... this is random, but you get the point).  And by default, if the user isn't found, return a 404 with the error that the user isn't found.

    Here's what it looks like using wget:

     

    dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml
    --2010-06-01 15:07:27--  http://localhost:8080/user/info.xml
    Resolving localhost... ::1, 127.0.0.1
    Connecting to localhost|::1|:8080... connected.
    HTTP request sent, awaiting response... 401 Unauthorized
    Authorization failed.

    dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml?id=2
    --2010-06-01 15:07:44--  http://localhost:8080/user/info.xml?id=2
    Resolving localhost... ::1, 127.0.0.1
    Connecting to localhost|::1|:8080... connected.
    HTTP request sent, awaiting response... 404 Not Found
    2010-06-01 15:07:44 ERROR 404: Not Found.

    dpp@bison:~/lift_sbt_prototype$ wget http://localhost:8080/user/info.xml?id=1
    --2010-06-01 15:24:12--  http://localhost:8080/user/info.xml?id=1
    Resolving localhost... ::1, 127.0.0.1
    Connecting to localhost|::1|:8080... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 274 [text/xml]
    Saving to: `info.xml?id=1'

    dpp@bison:~/lift_sbt_prototype$ cat info.xml\?id\=1 
    <?xml version="1.0" encoding="UTF-8"?>
    <User id="1" firstName="Elwood" ... validated="true" superUser="false"></User> 

    One more thing about Box and Option... they lead to less complex, more maintainable code.  Even if you didn't know anything about Scala or Lift, you can read the XML serving code and the console exchange and figure out what happened any why it happened.  This is a lot more readable than deeply nested if statements. And if it's readable, it's maintainable.

    I hope this is an understandable introduction to Scala's Option class and 'for' comprehension and how Lift makes use of these tools.