Showing posts with label Scalatra. Show all posts
Showing posts with label Scalatra. Show all posts

Sunday, June 12, 2011

Scalatra MongoDB template project using SBT 0.10.x

I spent some time this weekend getting a basic Scalatra/Casbah/MongoDB project (much like the one I covered in this post) set up using SBT 0.10.0.

If you have looked at the newest version of SBT, you know that a lot has changed. SBT 0.7.x projects will not build under the newer versions without a bit of migration. I ran into some hiccups along the way, so I set up a template project to avoid having to figure it out again. It uses Nathen Hamblen's fantastic giter8(g8) tool to allow you to set up a new project in seconds. Hopefully other g8 users find this template useful.

Changes in SBT 0.10.x

I just want to note some of the changes I ran into in setting up this very simple project:

  • build.sbt file - SBT 0.10.x offers an alternative to the /project/build/Project.scala configuration file - you can put a *.sbt file in the root of your project and configure your project there using the new SBT DSL. You can still configure your project using Scala, but the API and file locations have changed. See the SBT documentation for details.

  • "jetty" context for the Jetty dependency - this one confused me as I didn't see it documented anywhere. The Jetty dependency in build.sbt should not use the "test" scope as it did with SBT 0.7.x but rather the "jetty" scope. Using "test" will cause the jetty-run command to silently fail in a most unhelpful way.

  • Jetty support no longer built-in - Unlike SBT 0.7.x which provided commands like jetty-run, jetty-stop and prepare-webapp for any Project extending DefaultWebProject, SBT 0.10.x requires a plugin to support web applications. See the project's homepage for setup and my template project for an example.


Template Project

You'll need to install SBT 0.10.x - README

You'll also need to install g8

Both tools are very easy one-time installs and worth having. Once they are installed, you should be able to run:


g8 JanxSpirit/scalatra-mongodb
cd *name-of-app*
sbt
> update
> jetty-run


You'll then be able to browse to the test resource to make sure it's working.

What's inside

The servlet provides two resources:
  1. 'test' - a 'Hello World' type resource just to make sure everything is working.

  2. 'msgs' - GET msgs to see all records currently in the database and a form to add another record - the form issues a POST to the same resource to add records

The project includes a few basic tests to get you started. Run 'sbt test' to see the test output.

The template allows you to configure where your MongoDB server is running, the name of your servlet and other parameters.

All source code is here: https://github.com/JanxSpirit/scalatra-mongodb.g8 I'd welcome any comments or suggestions or pull requests. Happy coding!

Tuesday, January 25, 2011

A Quick WebApp with Scala, MongoDB, Scalatra and Casbah

Here's a nice way to get a web service up and running quickly using Scala, Scalatra, SBT and MongoDB. I'm going to assume you have Scala installed but nothing beyond that. Feel free to skip the episodes you've already seen.

Install SBT
Simple Build Tool (SBT) is a build tool for Scala, and it's just super. To install it, download the jar from here: http://code.google.com/p/simple-build-tool/downloads/list

Put that jar someplace and the make a little script along the lines of this:
java -Xmx1512M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=512m -jar `dirname $0`/sbt-launch.jar "$@"
Save that alongside the jar you downloaded. Make it executable and add it to your PATH.

MongoDB
MongoDB ( http://www.mongodb.org ) is a document database, meaning it persists JSON-like documents of arbitrary structure. It's very fast and offers very flexible ad hoc querying. A MongoDB document looks like this:
{
_id : ObjectId("4d2167a24d2fa1a764a6f0fa"),
date : "Sun Jan 02 2011 22:07:30 GMT-0800 (PST)",
body : "My blog post!",
upvotes : 3,
downvotes : 0,
comments : [
{
author : "The Dude",
body : "The Dude abides"
},
{
author : "Walter",
body : "Over the line!"
}
]
}
You don't create joins in the database - each document is schema-less and unrelated to other documents, even those in its collection (sort of like a SQL table in MongoDB parlance), But you can do cool stuff like index on those nested comments. Here's how to install it and get started:

Download the tarball for your architecture: http://www.mongodb.org/downloads

Expand it somewhere and add the bin directory to your PATH if you want to.

Run something like this to start the MongoDB server:
$MONGO_HOME/bin/mongod --dbpath=/path/to/place/to/save/mongodb/data
Connect to it and try it out like so:
$MONGO_HOME/bin/mongo
'mongo' is the MongoDB shell, which is a complete Javascript environment and connects to the local server on the default port. Create the example document above like this:
> use temp
switched to db temp
> db.demo.insert({date:new Date(), body:'My blog post!', upvotes: 3, dowvotes: 0, comments: [{author: 'The Dude', body: 'The Dude abides'},{author: 'Walter', body: 'Over the line!'}]})
> db.demo.find()
It's an excellent tool and a lot of fun. There are great docs at http://mongodb.org

Creating the SBT Project
Let's create an application. Make yourself a directory, navigate to it and run sbt. It will guide you through a wizard experience thusly:
$ sbt
Project does not exist, create new project? (y/N/s) y
Name: Demo
Organization: com.demo
Version [1.0]:
Scala version [2.7.7]: 2.8.1
sbt version [0.7.4]:
Getting Scala 2.7.7 ...
...more stuff...
[success] Successfully initialized directory structure.
...more stuff...
[info] Building project Demo 1.0 against Scala 2.8.1
[info] using sbt.DefaultProject with sbt 0.7.4 and Scala 2.7.7
>
Leave that shell open to the sbt prompt. In your editor of choice, create a file in your project directory named
project/build/Project.scala
Add the following to it:
import sbt._
class build(info: ProjectInfo) extends DefaultWebProject(info) {
// scalatra
val sonatypeNexusSnapshots = "Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
val sonatypeNexusReleases = "Sonatype Nexus Releases" at "https://oss.sonatype.org/content/repositories/releases"
val scalatra = "org.scalatra" %% "scalatra" % "2.0.0.M2"

// jetty
val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.22" % "test"
val servletApi = "org.mortbay.jetty" % "servlet-api" % "2.5-20081211" % "provided"

//casbah
val casbah = "com.mongodb.casbah" %% "casbah" % "2.0.1"
}
In your sbt shell, run
reload
and then
update
SBT will download dependencies ending with a message of success.

Add a Scalatra GET handler
Scalatra is refreshingly simple. To create a Scalatra servlet with a simple GET handler, using Scala's support for XML literals, create a file like src/main/scala/WebApp.scala with the following content:
import org.scalatra._

class WebApp extends ScalatraServlet {
get("/hello") {
<html><head><title>Hello!</title></head><body><h1>Hello</h1></body></html>
}
}
web.xml

One last bit of setup is to add the Scaltra servlet to the web.xml. Create a file named /src/main/webapp/WEB-INF/web.xml with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
<web-app>

<servlet>
<servlet-name>WebApp</servlet-name>
<servlet-class>WebApp</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>WebApp</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
Back at the SBT prompt you just need to
reload
and then do the following to start jetty and listen for source file changes:
jetty-run
~prepare-webapp
Open a browser to http://localhost:8080/hello and you should get a working Scalatra app. Change the message to something more interesting, like "Hello World" or "Goodnight Moon", save the source file, and SBT will recompile and redeploy. Just reload the web browser to see your changes.

Parameters

Add a path parameter to your GET call thusly:

get("/hello/:name") {
<html><head><title>Hello!</title></head><body><h1>Hello {params("name")}!</h1></body></html>
}
Save the file, browse to http://localhost:8080/hello/handsome

Oh yeah!

A Form

Add a form like this:
 get("/msgs") {
<body>
<form method="POST" action="/msgs">
Author: <input type="text" name="author"/><br/>
Message: <input type="text" name="msg"/><br/>
<input type="submit"/>
</form>
</body>
}
You can see the fruits of your labor at http://localhost:8080/msgs

Casbah

Casbah is the officially supported Scala driver for Mongo. It wraps the Java driver and makes accessing MongoDB straightforward using idiomatic Scala. http://api.mongodb.org/scala/casbah/2.0.2/index.html

You already have the driver dependency in your project, but you'll need a few new imports:
import com.mongodb._
import com.mongodb.casbah.Imports._
import scala.xml._
And a MongoDB connection that can be shared across the application:
val mongo = MongoConnection()
val coll = mongo("blog")("msgs")
Add a method to handle the form POST:

post("/msgs") {
val builder = MongoDBObject.newBuilder
builder += "author" -> params("author")
builder += "msg" -> params("msg")

coll += builder.result.asDBObject
redirect("/msgs")
}
Slick, right? That's all it takes to add a new document to the "msgs" collection in the "blog" db. You don't even need to create the db and collection first - just make sure MongoDB is running on the local host at the default port 27017.

Now you should adjust the form page to display the existing messages. This code treats iterates over the whole collection - in a real-world application you'd want paging, querying, filtering etc.

Add this code to your get() method just after the body tag:
 
<ul>
{for (l <- coll) yield <li>
From: {l.getOrElse("author", "???")} -
{l.getOrElse("msg", "???")}</li>}
</ul>

Once your app builds and you reload the page, your form should work to add new messages with authors and display them.

In Conclusion

I've found this stack a great way to whip up a quick example, prototype or test. You can use SBT to build a war (the 'package' command) and deploy it to any container you want.

I've barely scratched the surface of any of these bits, but hopefully piqued some interest in some cool tools. There are great docs and examples for all of these projects, so dig in and have fun!

Source code:

project/build/Project.scala:

import sbt._
class build(info: ProjectInfo) extends DefaultWebProject(info) {
// scalatra
val sonatypeNexusSnapshots = "Sonatype Nexus Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
val sonatypeNexusReleases = "Sonatype Nexus Releases" at "https://oss.sonatype.org/content/repositories/releases"
val scalatra = "org.scalatra" %% "scalatra" % "2.0.0.M2"

// jetty
val jetty6 = "org.mortbay.jetty" % "jetty" % "6.1.22" % "test" servletApi = "org.mortbay.jetty" % "servlet-api" % "2.5-20081211" % "provided"

//casbah
val casbah = "com.mongodb.casbah" %% "casbah" % "2.0.1"
}

src/main/scala/WebApp.scala:

import org.scalatra._
import com.mongodb.casbah.Imports._
import scala.xml._
import com.mongodb._

class WebApp extends ScalatraServlet {

val mongo = MongoConnection()
val coll = mongo("blog")("msgs")

get("/hello/:name") {
<html><head><title>Hello!</title></head><body><h1>Hello {params("name")}!</h1></body></html>
}

get("/msgs") {
<body>
<ul>
{for (l <- coll) yield <li>
From: {l.getOrElse("author", "???")} -
{l.getOrElse("msg", "???")}</li>}
</ul>
<form method="POST" action="/msgs">
Author: <input type="text" name="author"/><br/>
Message: <input type="text" name="msg"/><br/>
<input type="submit"/>
</form>
</body>
}

post("/msgs") {
val builder = MongoDBObject.newBuilder
builder += "author" -> params("author")
builder += "msg" -> params("msg")

coll += builder.result.asDBObject
redirect("/msgs")
}
}