Chapter 11. Dependency injection in action!

"A computer lets you make more mistakes faster than any invention in human history—with the possible exceptions of handguns and tequila."

—Mitch Radcliffe

In this chapter we'll put all of the things you've learned so far in this book into practice in a real, working application. While there probably aren't enough pages to demonstrate a large enterprise application, we'll look at a succinct application that gives us all the salient features of a broader program.

This program will start from bootstrapping the injector in a web application and walk through designing services for persistence, transactions, and user interactivity. We'll also show how to apply some useful design patterns (such as the Provider pattern from chapter 5) and ways to properly leverage scope, lifecycle, and interception (AOP).

Let's start by defining what this program will be.

Crosstalk: a Twitter clone!

I've chosen to do a simple, trivial clone of the popular microblogging service Twitter. (This isn't in the least bit because I spend large chunks of my day on Twitter!) While I was writing this chapter, I thought crosstalk was cool code name.

Crosstalk serves as a good illustration of a straightforward yet vastly scalable website that has plenty of user interactivity and data processing needs. These make it an ideal problem domain to showcase the wonderful patterns of dependency injection.

Its requirements are diverse—ranging from persistence and presentation to security and user management. This gives us a good basis for drawing an illustrative architecture from start to finish, in a few pages. First, let's look at these requirements.

Crosstalk's requirements

Our thesis is fairly straightforward: crosstalk allows users to write short text messages (called tweets) on their home page. This engenders some broad requirements. We need to

  • Authenticate users when they log on

  • Secure their home pages

  • Persist tweets in a database

We also need to ensure that the site is scalable and concurrent. In other words, it must be easily tolerant to many users accessing their home pages at once. Given all these requirements, it follows that crosstalk has several layers:

  • The presentation layer (website)

  • The persistence layer (database mechanism)

  • The security layer (authentication and securing pages)

We'll build this application using Google Guice as the injector and Google Sitebricks as the web application framework. Google Sitebricks is a simple, statically typed development system for rendering web pages. It follows the REST idiom, which makes it ideal for building HTML websites.

For persistence, we'll use Hibernate and warp-persist (seen in chapter 8) as the integration bridge with Guice. Let's get started.

Setting up the application

First, we'll need a layout and structure for the crosstalk application. This needs to follow the Java Servlet standard so that it can be deployed in a servlet container like Apache Tomcat or Mort Bay Jetty. We'll use Jetty for this illustration, since it's very simple to get up and running with. And as the database, we'll go with Hypersonic (sometimes called HSQL), which is an in-memory SQL database that's easy to set up and configure.

Here's the structure we'll start out with for the project (see figure 11.1):

  • src—The main source directory, with all our classes

  • test—A test sources directory, for our Jetty launcher

  • web—The directory for all the web resources (HTML templates, CSS stylesheets)

  • web/WEB-INF—A directory required by the servlet container to look for deployment descriptors

The file web.xml is a deployment descriptor that instructs the servlet container about how to configure this web application. We'll use a web.xml file very similar to the one used in chapter 5, when we were dealing with web scopes.

Once you have that layout set up (see figure 11.1), open a project in your favorite IDE and add the libraries shown in table 11.1 to the classpath:

The layout of directories for the crosstalk application 

Figure 11.1. The layout of directories for the crosstalk application<br></br> 

Table 11.1. Libraries<br></br> 

Library

Description

guice-2.0.jar

Google Guice core

http://code.google.com/p/google-guice/

aopalliance.jar

Guice AOP interface (included with Guice)

google-sitebricks.jar

Google's Sitebricks web framework

http://code.google.com/p/google-sitebricks/

guice-servlet-2.0.jar

Servlet integration for Guice (included with Guice

hibernate3.jar

Hibernate core persistence framework

https://www.hibernate.org/

hibernate-annotations.jar

Annotations for Hibernate

https://www.hibernate.org/

ejb3-persistence.jar

Annotations for Hibernate (included with Hibernate Annotations)

dom4j-1.6.1.jar

Dom4j XML parser (included with Hibernate)

jta.jar

Java Transaction API (included with Hibernate)

cglib-nodep-2.1_3.jar

CGLib used by Hibernate to proxy objects

antlr-2.7.5h3.jar

Antlr compiler library (included with Hibernate)

commons-collections.jar

Apache Collections library (included with Hibernate)

commons-logging.jar

Apache Commons-Logging (required by Hibernate for log output)

warp-persist-2.0-20090214.jar

Warp-persist integrates Hibernate with Guice

http://www.wideplay.com/guicewebextensions2

hsqldb.jar

Hypersonic In-Memory SQL database

http://hsqldb.org/

servlet-api-2.5-6.1.9.jar

Java Servlet API (included with Jetty)

jetty-6.1.9.jar

Mort Bay Jetty

http://www.mortbay.org/jetty

jetty-util-6.1.9.jar

Utilities for working with Jetty

jcip-annotations.jar

The set of annotations used in chapter 10 for thread-safety documentation

http://www.javaconcurrencyinpractice.com/

Tip

To add these libraries to the classpath in IntelliJ IDEA, open Settings > Project Settings and select the main (crosstalk) module. Then open the Dependencies tab and click Add. Now select Project Library and Add Jar Directory, choosing the directory where all these jars reside (or add them individually using the single-entry module library option).

Once you have all of those lined up, you should be good to go. If you aren't using an IDE, you can place all the jars in a lib directory and specify them individually on the command line.

Now the central configuration for our application will reside in CrosstalkBootstrap. This is a simple Java class, a subclass of GuiceServletContextListener, from which we'll tell Guice how to wire our services together, as shown in listing 11.1.

Example 11.1. The Guice configuration for crosstalk (also creates the injector)

public final class CrosstalkBootstrap extends GuiceServletContextListener {

    @Override
    protected Injector getInjector() {

        //bind in all of our service dependencies
        final Module services = new ServicesModule();

        //tell Sitebricks to scan this package
        final Module sitebricks = new SitebricksModule() {
            protected void configureSitebricks() {
                scan(CrosstalkBootstrap.class.getPackage());
            }
        };

        //map all incoming requests through the PersistenceFilter
        final Module servlets = new ServletModule() {
                protected void configureServlets() {
                    filter("/*").through(PersistenceFilter.class);

                     install(sitebricks);
                }
        };
//finally, create the injector with all our configuration
        return Guice.createInjector(services, servlets);
    }
}

It looks pretty simple, but let's examine what this does. By subclassing GuiceServletContextListener, we're able to create and register our own injector with guice-servlet and the servlet container itself. This listener must also be registered in web.xml so that it can be called when the application is deployed and made ready to run, as shown in listing 11.2.

Example 11.2. Web.xml config for guice-servlet and a crosstalk-specific listener

<?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_2_5.xsd"
           version="2.5">

    <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <listener>
        <listener-class>com.wideplay.crosstalk.CrosstalkBootstrap</listener-
     class>
    </listener>

</web-app>

Web.xml also creates a filter mapping so that all incoming requests are passed through the guice-servlet GuiceFilter. This allows guice-servlet to reroute requests through the filters and servlets configured in Guice's servlet pipeline, allowing us to take advantage of dependency injection idioms, lifecycle, scope and interception. We saw a setup very similar to this in chapter 5. GuiceFilter is provided by guice-servlet and requires no special treatment. We map it to /* to indicate that all URLs are to be filtered through it:

<?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_2_5.xsd"
           version="2.5">

    <filter>
        <filter-name>guiceFilter</filter-name>
        <filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>

    <filter-mapping>
        <filter-name>guiceFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

...

</web-app>

This way, all incoming requests are rerouted through guice-servlet's GuiceFilter, allowing it to process requests that are specific to crosstalk and pass through requests for static resources like CSS stylesheets and images. Application-specific requests are handled by Google Sitebricks pages, which we'll now go about setting up.

Configuring Google Sitebricks

Since we'll be using the Google Sitebricks web framework, we'll need to do extra configuration to tell it how to behave. Google Sitebricks has a simple philosophy where individual HTML pages are modeled as Java classes with a backing HTML template. Each such page class is registered against URL patterns in much the same manner as a servlet (or filter). We'll see how this works in a second. But first, let's tell guice-servlet to route all requests through Google Sitebricks, as shown in listing 11.3.

Example 11.3. CrosstalkBootstrap from listing 11.1

CrosstalkBootstrap from listing 11.1

The line in bold directs Guice to install our web framework, meaning that Google Sitebricks gets a crack at all user requests and can decide which ones to process and which ones it can safely pass on to the servlet container (such as static resources).

Now, we can proceed to look at the Google Sitebricks configuration:

CrosstalkBootstrap from listing 11.1

The method scan(...) tells Google Sitebricks which packages to work with. You may specify as many packages as needed this way.

By providing it with CrosstalkBootstrap's package, we tell Google Sitebricks to scan the entire package tree beginning with com.wideplay.crosstalk and to look for page classes and templates to serve.

Finally, we install one more module in our injector, which will handle all application services. This is, appropriately, the ServicesModule:

CrosstalkBootstrap from listing 11.1

The Guice.createInjector() method is a varargs method, meaning that it takes any number of arguments. This is convenient for us as we can pass in our two modules, services and servlets, that configure our persistence and security, respectively. Note that Google Sitebricks web pages are processed by the SitebricksModule. We'll look at the services module in some more detail shortly, but first, let's examine the application's package structure.

Crosstalk's modularity and service coupling

In this section, we'll look at how crosstalk is broken down into modules by package, service type, and contract. We'll see some of the concepts presented in chapter 4 on modularizing code and in the chapter 10 on packaging clean designs that separate areas of concern.

Crosstalk's packages also expose no implementation details, so that accidental coupling of services does not occur. All collaboration between modules happens through publicly exposed interfaces, with well-defined contracts. Let's see how this structure is achieved.

Crosstalk's packages are organized very simply (shown in figure 11.2).

The layout of packages in the crosstalk application's src directory 

Figure 11.2. The layout of packages in the crosstalk application's src directory<br></br> 

They are divided among the core application services, Google Sitebricks web pages, and any data model classes:

  • com.wideplay.crosstalk.web—The presentation layer (all page classes go here)

  • com.wideplay.crosstalk.services—Persistence and security layer

  • com.wideplay.crosstalk.tweets—The domain model package

Notice that the services package exposes only interfaces (apart from the Guice ServicesModule configuration class). In chapter 4 we discussed the concept of loose coupling. This allows us to modify specific implementation details without affecting the presentation layer in any way. For example, we may choose to persist the data in a cluster data store rather than Hibernate and HSQL. By changing the implementation of classes in the services package, we can do this with no impact to the rest of the application. All implementation classes are hidden away as package-local. Figure 11.3 provides a complete picture of this approach.

All the classes and packages in the eventual crosstalk application 

Figure 11.3. All the classes and packages in the eventual crosstalk application<br></br> 

You may also notice that the web pages and the data model classes are public. This is because they are used directly by the framework (Google Sitebricks in the web pages' case and Hibernate in the data model's) and configured using flexible annotations, which already alleviate any coupling perils.

Now that we've taken a bird's-eye tour of the structure, let's get down to it. First, the business end of crosstalk, its user-facing presentation layer.

The presentation layer

Let's look at some of crosstalk's functionality. Crosstalk's core requirement is the ability to post tweets on a user's home page. For this, we create a class HomePage, with a corresponding template in the web/ resources directory. HomePage looks like figure 11.4.

A user's home page with no content-it allows users to blog new tweets in a text box. 

Figure 11.4. A user's home page with no content-it allows users to blog new tweets in a text box.<br></br> 

The Java source code behind HomePage is shown in listing 11.4.

Example 11.4. HomePage Sitebricks page class models a crosstalk user's home page

package com.wideplay.crosstalk.web;

@At("/home") @Select("action") @RequestScoped
public class HomePage {
    //user context, tracks current user
    private User user;

    //page state variables
    private List<Tweet> tweets;
    private Tweet newTweet = new Tweet();

    //service dependencies
    private final TweetManager tweetManager;

    @Inject
    public HomePage(TweetManager tweetManager, User user) {
        this.tweetManager = tweetManager;
        this.user = user;
    }

    @Get("logout")
    public String logout() {
        user.logout();

        return "/login?message=Bye.";
    }

    @Get
    public String get() {

        //load tweets for current user
this.tweets = tweetManager.tweetsFor(user.getUsername());

        //stay on current page
        return null;
    }

    @Post
    public String post() {
        newTweet.setAuthor(user.getUsername());

        //contents are in newTweet, add it to the data store
        tweetManager.addTweet(newTweet);

        //redirect back to this page using a GET
        return "/home";
    }

    //getters/setters...

    public String getUser() {
        return user.getUsername();
    }

    public List<Tweet> getTweets() {
        return tweets;
    }

    public Tweet getNewTweet() {
        return newTweet;
    }
}

This looks pretty involved. Let's see what each bit actually means. First, we annotate the class with the @At annotation, which tells Google Sitebricks to serve this page at the given URI:

@At("/home") @On("action") @RequestScoped
public class HomePage { .. }

Now, whenever a user types in URL http://localhost:8080/home (assuming the application is deployed at localhost:8080), the HomePage will be served up by Google Sitebricks. We also see the familiar @RequestScoped annotation, which tells Guice to create a new instance of HomePage for every incoming request. This is important because it allows us to work with state that's specific to a user without stepping on other users' data, accidentally.

Finally, the @On annotation is used to resolve events to fire against. For now, let it suffice to say that @On controls which method annotated with @Get is called based on an incoming request parameter named action.

The visual companion to this page class is its HTML template. This template contains what a user will see. Let's see how it's built.

The HomePage template

HomePage's template is a very simple HTML file. It contains dynamic text, which is provided by binding its data to the request-scoped instance of class HomePage, as shown in listing 11.5. It also contains a link to a stylesheet.

Example 11.5. An HTML template that displays tweets, backed by HomePage

An HTML template that displays tweets, backed by HomePage

This line in particular is interesting because it resolves to a property of the HomePage class (that is, one that is accessed via a getter method):

<h2>Tweets by ${user}</h2>

This heading is dynamically evaluated at runtime by calling HomePage.getUser(). HomePage.getUser() returns the username of the current user logged in to crosstalk:

public String getUser() {
    return user.getUsername();
}

One other interesting part of the template is the @Repeat annotation:

@Repeat(items=tweets, var="tweet")
<div class="box">
    <div class="box-content">
        ${tweet.text} (${tweet.createdOn})
    </div>
</div>

This tells Google Sitebricks to repeat the annotated tag (in this case, <div class="box"> over all the items in HomePage's property tweets. For each element in tweets, Google Sitebricks will render out the div tags expanding tweet.text and tweet.createdOn properties. In other words, this renders out the list of tweets posted by a user on their home page (see figure 11.5).

A user's home page with blogged content 

Figure 11.5. A user's home page with blogged content<br></br> 

The other significant part of this template is the form. Users type their new tweets into a text field and post them by submitting the form:

<div>What are you doing right now?</div>

<form action="/home" method="post">
    <textarea name="newTweet.text" rows="5" cols="60" />
    <input type="submit" value="update"/>
</form>

Since the form's textarea is bound to newTweet.text, Google Sitebricks will attempt to write the incoming form post to HomePage's property newTweet:

@At("/home") @On("action") @RequestScoped
public class HomePage {

    ...

    //page state variables
    private List<Tweet> tweets;
    private Tweet newTweet = new Tweet();

   ...
}

This is accomplished by navigating the getNewTweet() method to the appropriate setter methods on class Tweet, in this case, setText(). We take care to create a new instance of Tweet every time HomePage is created, thus every time it is requested, ensuring that tweets are never overwritten by accident.

Each new tweet entry by a user is recorded and modeled as an instance of class Tweet.

The Tweet domain object

Tweet is the sole class in our domain model, and it consists of simple data fields describing a message, its creation date, and its author, as shown in listing 11.6.

Example 11.6. Tweet models a tweet message for capturing data and storing it

Tweet models a tweet message for capturing data and storing it

This class also contains a private field id, which is used by Hibernate to track tweets in the database. This field is annotated with some boilerplate, identifying it as a primary key that's generated automatically:

@Id @GeneratedValue
 private Long id;

The class itself is annotated with @Entity to indicate to Hibernate that this class is to be treated as a data entity.

Note

Internally, @Entity helps Hibernate separate classes that are mapped to tables from those that may be embedded as additional columns in a larger database table.

All of Tweet's properties have JavaBeans getter and setter methods as per the convention with Hibernate. So far we've seen how this models all of our data and keeps it organized. We need to go a step further and provide equals() and hashCode() methods to ensure that instances of Tweet behave properly in collections. This is also important for any class that Hibernate uses, since it will attempt to compare copies when optimizing for cached access.

equals() and hashCode() are quite straightforward, as shown in listing 11.7; they take all three data fields into account since together they form the identity of the Tweet.

Example 11.7. equals() and hashCode() for class Tweet, to compare instances

equals() and hashCode() for class Tweet, to compare instances

The idea is that objects are considered equal if they have the same values for author, text, and createdOn. Put another way, it's impossible to have all three match for two independent tweets. If two are posted at the same time, they will be from different authors. And if they are from the same author, they will necessarily have different creation times.

Users and sessions

In order to track users across independent requests, we must use an HTTP session. However, rather than interact with it directly, crosstalk takes advantage of scoping. The class User serves as a context for the current user of the system. Wherever it's referenced from, User points to the user whose HTTP session is currently active. In listing 11.8 we mark User as scoped to the HTTP session by using the @SessionScoped annotation.

Example 11.8. Session-scoped User tracks information about the current user

@SessionScoped @NotThreadSafe
public class User {
    private String username;

    public String getUsername() {
        return username;
    }

    //logs in a user, by setting username to the current session
    public void login(String username) {
        this.username = username;
    }

    //logs out a user, by clearing username from session
    public void logout() {
        this.username = null;
    }
}

User does nothing more than store a username for the user who logs in and clears that name when the user logs out. Web page classes and services alike can check to find out whether someone is logged in and, if so, who is logged in by querying this service.

In HomePage, User is directly injected directly as a dependency:

@At("/home") @Select("action") @RequestScoped
public class HomePage {
    //user context, tracks current user
    private final User user;

    //page state variables
    private List<Tweet> tweets;
    private Tweet newTweet = new Tweet();

    //service dependencies
    private final TweetManager tweetManager;

    @Inject
    public HomePage(TweetManager tweetManager, User user) {
        this.tweetManager = tweetManager;
        this.user = user;
    }

    ...
}

This is safe because the session scope is wider than the request scope. This means that HomePage instances are shorter lived than User instances and therefore run no risk of scope-widening injection. HomePage retrieves a user's tweets by querying the TweetManager for tweets by username:

@Get
public String get() {
//load tweets for current user
this.tweets = tweetManager.tweetsFor(user.getUsername());

//stay on current page
return null;
}

This method is called in normal operation when an HTTP GET request is received by the web server. The annotation @Get indicates to Google Sitebricks to use this method as an event handler prior to rendering the page. Once tweets are loaded (and stored in the tweets field), the template is rendered displaying all of a user's tweets. You may also have noticed a "Sign out" link on the HomePage, which a user clicks to leave the session:

<a href="http://manning.com/prasanna">Help</a> |
<a href="?action=logout">Sign out</a>

HomePage reacts to the sign-out action by logging the user out from the User service:

@Get("logout")
public String logout() {
    user.logout();

    return "/login?message=Bye.";
}

Because of the @On("action") annotation at the top of the class, Google Sitebricks knows to call a different event handler. This time, method logout() is called since it's annotated with the value-matching action (@Get("logout")). The logout() method then redirects the user to a login page, where they must log in again to continue using the site:

@Get("logout")
 public String logout() {
     user.logout();

     return "/login?message=Bye.";
 }

The login page is both the exit and entry point for the crosstalk website and is also modeled as a Google Sitebricks page.

Logging in and out

LoginPage, like HomePage, has a Java class and companion template, as shown in figure 11.6.

The template is fairly straightforward. It accepts a username and password, as shown in listing 11.9 (see figure 11.6 for the web version).

Example 11.9. An HTML template for LoginPage displays login input fields

An HTML template for LoginPage displays login input fields
All users must log in to use their home page on crosstalk. 

Figure 11.6. All users must log in to use their home page on crosstalk.<br></br> 

This form posts back to LoginPage with the entered credentials. LoginPage then uses this information to authenticate the user against a UserManager, as shown in listing 11.10.

Example 11.10. Page class LoginPage authenticates users logging into crosstalk

@At("/login") @RequestScoped
public class LoginPage {
    private String username;
private String password;

    private String message = "";

    //service dependencies
    private final UserManager userManager;
    private final User user;

    @Inject
    public LoginPage(UserManager userManager, User user) {
        this.userManager = userManager;
        this.user = user;
    }

    @Post
    public String login() {

        //attempt to authenticate the user
        if (userManager.authenticate(username, password))
            user.login(username);
        else {
            //clear user context from session
            user.logout();

            //stay on this page with error
            return "/login?message=Bad+credentials.";
        }

        //redirect to home page if successfully logged in
        return "/home";
    }

    //getters/setters...
    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

LoginPage has the standard getters and setters to expose its properties to the template and form input. Method login() asks UserManager (a data service) whether the username and password are valid:

@Post
public String login() {

    //attempt to authenticate the user
    if (userManager.authenticate(username, password))
        user.login(username);
else {
        //clear user context from session
        user.logout();

        //stay on this page with error
        return "/login?message=Bad+credentials.";
    }

    //redirect to home page if successfully logged in
    return "/home";
}

If they are valid, login() logs the user in via the session-scoped User service shown earlier. If not, a redirect is sent back to the login screen with an error message:

@Post
public String login() {

    //attempt to authenticate the user
    if (userManager.authenticate(username, password))
        user.login(username);
    else {
        //clear user context from session
        user.logout();
        return "/login?message=Bad+credentials.";
    }

    //redirect to home page if successfully logged in
    return "/home";
}

This results in the "Bad credentials" error message as shown in figure 11.7.

Once successfully logged in, the user is sent to their home page to tweet away.

Bad usernames or passwords are rejected, appropriately. 

Figure 11.7. Bad usernames or passwords are rejected, appropriately.<br></br> 

The persistence layer

Data services that crosstalk uses are fairly simple. There's one for managing users (UserManager) and one for managing tweets (TweetManager)—not rocket science. Their implementations are subtly different. Let's look at the TweetManager implementation first, as shown in listing 11.11.

Example 11.11. Implementation for the TweetManager interface using Hibernate

@Immutable @Singleton
class HibernateTweetManager implements TweetManager {

    //the provider pattern helps us prevent scope-widening of sessions
    private final Provider<Session> session;

    @Inject
    public HibernateTweetManager(Provider<Session> session) {
        this.session = session;
    }

    @Finder(query = "from Tweet where author = :author")
    public List<Tweet> tweetsFor(@Named("author") String author) {

        //this method is intercepted by warp-persist DynamicFinders
        // and converted into a query. So you should not see an empty
        // list unless the database contains no Tweets for 'author'.
        return Collections.emptyList();
    }

    public void addTweet(Tweet tweet) {
        session.get().save(tweet);
    }
}

HibernateTweetManager implements the TweetManager interface by storing and querying tweets from the database using Hibernate's services. To add a new tweet, we use a session to the database and save a new instance of the Tweet data object:

public void addTweet(Tweet tweet) {
    session.get().save(tweet);
}

We use the Provider pattern here to avoid scope-widening the session, which is implicitly request scoped. I say implicitly, because its behavior is managed by warp-persist, as you'll see. HibernateTweetManager, like all other classes in the services package, is a thread-safe, immutable singleton. Querying objects is taken care of us by warp-persist's Dynamic Finders utility. Annotating the method tweetsFor() with @Finder tells warppersist to intercept and replace the method with logic that runs the bound query:

@Finder(query = "from Tweet where author = :author")
public List<Tweet> tweetsFor(@Named("author") String author) {

    // this method is intercepted by warp-persist DynamicFinders
    // and converted into a query. So you should not see an empty
    // list unless the database contains no Tweets for 'author'.
    return Collections.emptyList();
}

The string "from Tweet where author = :author" is an HQL (Hibernate Query Language) query, which tells Hibernate to load all tweets with the matching author. The method parameter author is annotated with an @Named("author") annotation, which binds the parameter into the query at the label :author.

Tip

For more information on warp-persist and dynamic finders, including tutorials, visit http://www.wideplay.com.

The UserManager service by contrast, does not use Hibernate to store users in the database. This is done as a matter of convenience to get us up and running quickly, as shown in listing 11.12.

Example 11.12. A UserManager that uses hard-coded user credentials

@Immutable @Singleton
class InMemoryUserManager implements UserManager {
    private final Map<String, String> users;

    public InMemoryUserManager() {
        final Map<String, String> users = new HashMap<String, String>();
        users.put("dhanji", "dirocks");

        //freeze the current hardcoded map so that it is thread-safe
        this.users = Collections.unmodifiableMap(users);
    }

//returns true if a username and password combination is valid
public boolean authenticate(String username, String password) {
        return users.containsKey(username)
            && users.get(username).equals(password);
    }
}

A simple hash table is stored in memory in this implementation, and the authenticate() method checks values in this hash table. We ensure that this hash table is immutable by wrapping it in an unmodifiable map. You should recall this best practice from chapter 10.

public InMemoryUserManager() {
    final Map<String, String> users = new HashMap<String, String>();
    users.put("dhanji", "dirocks");

    //freeze the current hardcoded map so that it is thread-safe
    this.users = Collections.unmodifiableMap(users);
}

This ensures that InMemoryUserManager is both highly concurrent and thread-safe (since it is immutable after construction). In a broader implementation, we'd use a Hibernate version of UserManager and have a separate set of pages for users to register and manage their accounts.

Now, let's look at interacting with a data store and retrieving and storing tweet data.

Configuring the persistence layer

As you saw in section 11.2, all the components in the services package are separately configured. The ServicesModule takes care of this for us and is shown in listing 11.13.

Example 11.13. Security and persistence configuration (see also listing 11.1)

package com.wideplay.crosstalk.services;

public final class ServicesModule extends AbstractModule {
    private static final String HIBERNATE_CONFIG = "hibernate.properties";

    @Override
    protected void configure() {

        //configure persistence services, using hibernate
        install(PersistenceService
                .usingHibernate()
                .across(UnitOfWork.REQUEST)
                .buildModule()
        );

        //configure hibernate with our tweet data model class
        bind(Configuration.class).toInstance(new AnnotationConfiguration()
                .addAnnotatedClass(Tweet.class)
                .setProperties(loadProperties(HIBERNATE_CONFIG))
        );

        //configure crosstalk data services
        bind(TweetManager.class).to(HibernateTweetManager.class);
        bind(UserManager.class).to(InMemoryUserManager.class);

        //configure the in-memory authenticator (with hard-coded users)
        final HttpSessionAuthenticationManager manager =
            new HttpSessionAuthenticationManager();
        bind(AuthenticationManager.class).toInstance(manager);

        //intercept any @Get or @Post method on any page except LoginPage
        bindInterceptor(
                not(subclassesOf(LoginPage.class)),
                annotatedWith(Get.class).or(annotatedWith(Post.class)),

                new SecurityInterceptor(manager)
        );
    }

    private static Properties loadProperties(String props) {
        ...
    }
}

First up, we configure warp-persist to use Hibernate and provide us with a Hibernate unit of work (session to the database) every time an HTTP request arrives:

install(PersistenceService
        .usingHibernate()
        .across(UnitOfWork.REQUEST)
        .buildModule()
);

Then we configure Hibernate itself by binding the Configuration class to an annotation-based implementation that contains our Tweet domain model class:

bind(Configuration.class).toInstance(new AnnotationConfiguration()
        .addAnnotatedClass(Tweet.class)
        .setProperties(loadProperties(HIBERNATE_CONFIG))
);

To this configuration, we also add Hibernate configuration properties that tell it which database to use and so on (see the source code accompanying the book for details). The two data services, for users and tweets, are bound directly to their interfaces:

bind(TweetManager.class).to(HibernateTweetManager.class);
bind(UserManager.class).to(InMemoryUserManager.class);

All this is straightforward. Then we bind another service whose purpose we'll see very shortly:

//configure the in-memory authenticator (with hard-coded users)
final HttpSessionAuthenticationManager manager =
    new HttpSessionAuthenticationManager();
bind(AuthenticationManager.class).toInstance(manager);

The AuthenticationManager is used by crosstalk's security layer to detect whether a user has been authenticated correctly. This level of indirection is required because we can't always directly inject User into our services. You'll see why in a second. But first, a SecurityInterceptor is bound to all methods annotated with @Get and @Post except the LoginPage:

//intercept any @Get or @Post method on any page except LoginPage
bindInterceptor(
        not(subclassesOf(LoginPage.class)),
        annotatedWith(Get.class).or(annotatedWith(Post.class)),

        new SecurityInterceptor(manager)
);

This interceptor is used to secure parts of the website by intercepting requests for web pages, allowing us to ensure that only public parts of the site (that is, the login page) are seen by users who are not authenticated and also that only authenticated users may post tweets to their home pages. Let's see how this security interceptor works.

The security layer

SecurityInterceptor is an AOP interceptor that runs on every event method, as shown in listing 11.14. It's intended to guard web pages and prevent them from being displayed to users who aren't properly authenticated.

Example 11.14. AopAlliance method interceptor applied across the app for security

class SecurityInterceptor implements MethodInterceptor {
    private final AuthenticationManager authenticationManager;

    public SecurityInterceptor(
AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    public Object invoke(MethodInvocation methodInvocation)
        throws Throwable {

        //proceed with normal execution if a user is logged in
        if (authenticationManager.isLoggedIn()) {
            return methodInvocation.proceed();
        }

        //redirect to login page if the user is not properly logged in!
        return "/login";
    }
}

SecurityInterceptor is an AopAlliance MethodInterceptor,[37] which is triggered every time an HTTP GET or POST handler method is fired by Google Sitebricks (except on LoginPage). If a user is logged in, then the method proceeds as normal. If not, a redirect is sent to the login page:

public Object invoke(MethodInvocation methodInvocation)
    throws Throwable {
    ...

    //redirect to the login page if user is not properly logged in!
    return "/login";
}

Method interceptors and their configuration are covered in chapter 8. If you're unsure of how the interception works, consult the section on proxying.

In the next section you'll see how the application's lifespan can be tied to a servlet engine so that it receives notifications about important events in its lifecycle.

Tying up to the web lifecycle

Our database persistence framework has several expensive operations it must perform when starting up and several cleanup operations when shutting down. These are independent of a unit of work and must occur outside the regular operational life of the web application.

Ideally, this coincides with the web application's own lifecycle and the servlet's init() and destroy() events. Fortunately for us, warp-persist's PersistenceFilter handles all this hard work. On an init(), it triggers the creation of connection pools, the creation of tables and resources, and so on. In our case, this also kick-starts the Hypersonic HQSL database in memory. On shutdown of the filter (when destroy() is called), it closes down the Hibernate SessionFactory.

In addition, the PersistenceFilter is necessary to open and close Hibernate units of work on every request. This filter must be registered before installing the SitebricksModule so that pages can benefit from the session to the database being open.

So far, we've looked at presentation, persistence and modularity. We've also used interception to provide security and tied everything up with the web lifecycle. Let's now get crosstalk running and give it the real litmus test.

Finally: up and running!

To run crosstalk, we'll create a simple class with a main method that fires up Jetty. We'll put this class in the test/ directory to distinguish it from the application proper. It looks like this:

public class Crosstalk {
    public static void main(String... args) throws Exception {
        Server jetty = new Server(8080);     //start at port 8080
        server.addHandler(new WebAppContext("web", "/"));

        jetty.start();
        jetty.join();
    }
}

Class Crosstalk creates an instance of Server, which represents the Jetty web server. We configure it to listen at port 8080 and use the web/ directory as its resource root. Finally, we also ask it to listen at the root URL context: "/".

Once Jetty is started, it's ready to begin servicing requests. We must use the join() method subsequently to prevent the application from exiting too early:

jetty.start();
jetty.join();

Now you're free to run the application (see figure 11.8). Point your browser to http://localhost:8080/login to see the login page and start using crosstalk!

See ya! 

Figure 11.8. See ya!<br></br> 

Summary

This chapter was an example walkthrough of a real, working web application that embodies many of the concepts you've studied thus far in this book. I chose crosstalk as the problem domain, which is a simple clone of the popular microblogging service Twitter.

Crosstalk starts life as a Guice-based application that uses Hibernate for persistence and Google Sitebricks as the web framework to display web pages and react to user input. We also use Mort Bay Jetty as the servlet container. The injector is bootstrapped (as described in chapter 2) by using the guice-servlet integration layer that allows us to create and configure the injector when the web application is deployed to the servlet container.

Crosstalk consists of three main packages:

  • web—Contains the web page classes and components

  • services—Contains the data and security services

  • tweets—Contains the domain model classes for data persistence

All the components of package services are exposed via interfaces, and their implementation details are hidden away as package-local. This is an example of good modularity and loose coupling as shown in chapters 4 and 10.

Web pages are all request-scoped and utilize class User, which is a session-scoped service that tracks the current user who is logged in. LoginPage ensures that a username is attached to the User service upon login, and HomePage signs a user out as appropriate. These web components exemplify the ideas presented in chapters 5 and 6 on scoping.

Since starting and stopping the persistence engine is an expensive operation and ideally coincides with the start and close of the web application itself, we register a wrapping PersistenceFilter that allows us to hook into the servlet lifecycle. Refer to our investigation of lifecycle and its semantics in greater detail in chapter 7.

The SecurityInterceptor was added to secure home pages from users who aren't logged in. This is an example of the behavior modification or AOP interception techniques presented in chapter 8.

Finally, we tied all of these patterns and techniques with the whole application to realize the full benefits of dependency injection!



[37] Refer to chapter 8 for details on AOP and interceptors.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.141.7.144