Grails Demo Walkthrough

Developing a Grails Web Application

Tutorial will: Walk through steps of creating a robust Grails web application.
Application name: grails-mongodb-demo
The Goal: To track and record persons addresses.
The Source: https://github.com/keaplogik/grails-mongodb-demo

The application will use the Grails Framework to develop a web application using:
  1. MongoDB
  2. Spring Security
  3. Apache CXF (TODO)
  4. Jetty Web Server
  5. Maven build integration Note: Tutorial follows maven but maven has been removed from the git project.

-Index

Installing the Grails SDK And MongoDB

In this example, we are using Mac OS X. Some adjustments are required for Windows
Install Grails SDK
  1. Go to the Grails Download Page and install the latest 2.2.x binary
  2. Extract the file to any location, for example: ~/sdks
  3. Add the bin directory to your path
    • Linux/Unix: export GRAILS_HOME=~/sdks/grails-2.2.x
      • Then add the bin directory to you PATH variable: export PATH="$PATH:$GRAILS_HOME/bin
    • Windows: set an environment variable under My Computer/Advanced/Environment Variables
Executing Commands
  1. Open a console
    • The grails commands are used in the manner: grails [command]
    • You can also launch the grails script executor by simply typing grails.
Once you create a Grails project, you can use this console to quickly create domain classes, controllers, or many other Grails components. This will not be neccesary in this demo, since we will be using the IntelliJ Idea 11 IDE
Install Mongo
  1. Follow the quickstart guide online. There are specific instructions for each OS.

Project Setup

Project setup requires working within a terminal/command window
1. Creating a Grails Application
    $ cd ~/dev/workspaces
    $ grails create-app grails-mongodb-demo
2. Integrating with Maven
    $ cd grails-mongodb-demo
    $ grails create-pom com.mycompany
    $ mvn compile
The create-pom command expects a group id as an argument. The name and the version are taken from the application.properties of the application. The Maven plugin will keep the version in the pom.xml in sync with the version in application.properties.
Referenced From Grail Docs
3. Integrating with IntelliJ
**Integrating is simple!
    $ grails integrate-with --intellij
The last thing to do is open the project in IntelliJ, and thats it! Your ready to get started. A Run configuration should already be available.
Note: The latest version of this demo in git does not use maven. Maven really isn't the best first choice. To configure without maven, do not add a POM.xml to directory, and add the following definitions to the file at 'grails-app/conf/BuildConfig.groovy'

On with the maven tutorial..

Important Note: When grails creates the Maven POM, it adds a configuration to the plugin; grails-maven-plugin: <fork>true</fork>. If you want to be able to kill run processes from IntelliJ, comment out this line.
back to top

Configuring the Plugins

When working with grails we don't typically have dependency jars. Instead, Grails uses plugins for it's management of artifacts. These plugins aren't just artifacts, but also are an extension of the Grails framework. Plugins are available for download on their website, and more information on their specs can be found in the documentation. You may also install plugins with the grails command:
grails install-plugin [plugin-name]
Since we are working with maven, the plugins will be managed by maven. These plugins are available in the Grails remote repository.
This repo must be listed in your list of remote repositories:
<repository>
    <id>grails-plugins</id>
    <name>grails-plugins</name>
    <url>http://repo.grails.org/grails/plugins</url>
</repository>
Grails typically handles it's plugin management in a configuration file "BuildConfig.groovy". This file can be located at:
../my-app/grails-app/conf/BuildConfig.Groovy
To enable the grails command line to read the POMs, you must modify this file to use Maven when resolving dependencies. I would suggest removing the plugins property from this script as well.
grails.project.dependency.resolution = {
    
    //Use maven for dependency resolution
    pom true
    repositories {
        
        mavenLocal()    
    }

    //REMOVE if using maven 
    plugins {
        ...
    }
}
back to top

1. MongoDB Plugin

By default, Grails comes pre-packaged with Hibernate. Since we are working with a NoSql data store, we will need to remove that plugin from our maven dependency list.
Go into your pom.xml, and remove the dependency:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>hibernate</artifactId>
        <version>2.1.1</version>
        <scope>runtime</scope>
        <type>zip</type>
    </dependency>
And replace it with:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>mongodb</artifactId>
        <version>1.2.0</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
IMPORTANT! When replacing hibernate with MongoDB GORM, you need to also:
  1. Remove the database-migration dependencey from your POM.
  2. To ensure removal, run command:
    1.  grails uninstall-plugin database-migration
    2.  grails uninstall-plugin tomcat
  3. Refresh by running: grails refresh-dependencies
  4. There are some compatibility issues between some versions of the mongoDB plugin and Grails. If you get any errors running this application, first please consult the mongoDB plugin page.
Now your configured for MongoDB, and data will store in BSON type objects, as opposed to a relational store.
back to top

2. Jetty Plugin

Grails, by default comes with Apache Tomcat as it's web server. To change to Jetty, we need to remove the Tomcat plugin dependency from our pom.xml.
To make Jetty 7.6.0 the development time container for Grails, replace Tomcat plugin dependency with:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>jetty</artifactId>
        <version>2.0.1</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
If your working in IntelliJ Idea, with auto-import turned on, IntelliJ will pull in the dependencies automatically.
back to top

3. Spring Security & Apache CXF Plugins

Add the following Maven dependencies to get Spring Security and Apache CXF support:
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>1.2.7.3</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>webxml</artifactId>
        <version>1.4.1</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
The WSClient plugin is based on GroovyWS - which uses CXF
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>ws-client</artifactId>
        <version>1.0</version>
        <scope>compile</scope>
        <type>zip</type>
    </dependency>
Finally
  1. Stop the current run process in IntelliJ, or if in console: Ctrl-C
  2. Run: mvn clean compile (May not be necessary, but ensures a clean refresh)
  3. Click Run button in IntelliJ. If your not in IntelliJ, on the command line run: mvn grails:run-app
The application is now up and running on Jetty with MongoDB. It's amazing how simple and rapid getting started with Grails is!
back to top

Building an Application

When building out the application, we will be using a combination of grails on the command line, and IntelliJ. You can decide what works best for creating skeleton domain and controller classes. But for this tutorial, I'm trying not to be completely bound to IntelliJ IDE.

The Domain Model

* The initial goal of this application, is to create and update person address history *
To get started we are going to first build out some skeleton classes for our model.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-domain-class org.mycompany.Person
    • In console prepend grails to the command.
  3. On the command line type: create-domain-class org.mycompany.Address
*Note: The names of database tables are automatically inferred from the name of the domain class.
Now we have skeleton classes for our two domain classes under: myapp/grails-app/domain
Open the Address class, and edit it as follows
class Address {

    String streetAddress;
    String city;
    String state;
    String zipCode;

    //Date moveInDate;
    //Date moveOutDate;

    static belongsTo = [person: Person]

    static constraints = {
        streetAddress(blank: false)
        city(blank: false)
        state(blank: false, size: 2..2)
        zipCode(blank: false, size: 5..5, validator: {val, obj -> val?.isNumber()})
        /*moveInDate(nullable: false, max:  new Date())
        moveOutDate(nullable: true, validator: { val, obj ->
            val?.after(obj.moveInDate)
        })*/
    }
}
  • The static field belongsTo indicates that the class Person assumes ownership of the relationship. i.e. An Address belongs to only one person (in this case).
  • The static field constraints, handles the validation logic for each field. Most fields cannot be blank, but the move out date can be blank if a person hasn't moved out yet. moveOutDate also has a custom validator to ensure the date is after the move in date. The dates are disabled to keep the demo simple. Feel free to uncomment them, to see how the scaffold view handles dates.
Open the Person class, and edit it as follows
class Person {

    String firstName;
    String lastName;

    static hasMany = [addresses: Address]

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
  • The static field hasMany Defines a one-to-many association between two classes. i.e. a Person has many Addresses.
Important Note: We are using mongoDB, so using relationships with the hasMany definition will not be efficient in a production setting. The reason I chose to use this, is because scaffold views do not support lists within domain classes. The mongo GORM plugin does support this. So a better way to create Person addresses would look as follows:
class Person {

    String firstName;
    String lastName;
    List<Address> addresses; //We are going to store an array of addresses in the person collection.

    static constraints = {
        firstName(blank: false)
        lastName(blank: false)
    }
}
For the sake of simplicity, we will use the former case.
You can view descriptions on all domain class features on the Grails Quick Reference Page
back to top

Controllers with Grails Scaffolding

Grails by default, has a decent UI for testing your new MVC components. Basic CRUD operations can be performed with one line of code in the model's controller. These views (called scaffolding views) are generated by the compiler when calling grails run-app
To get started we are going to create controllers for our model classes.
  1. In IntelliJ, press command-alt-G or right click in project view -> Grails -> Run Target
    • If on command line, cd to your application directory
  2. On the command line type: create-controller org.mycompany.Person
    • In console prepend grails to the command.
  3. On the command line type: create-controller org.mycompany.Address
What this does is creates two controller classes named PersonController, and AddressController for our model.
Open up PersonController class.
  • remove the line def index() { } and replce it with def scaffold = Person
class PersonController {

    def scaffold = Person
}
Now do the same for the AddressController
class AddressController {

    def scaffold = Address
}

Configure GRAILS-Boootstrap

The Configuration class: Bootstrap is useful for, because you can use it to add data to the application when the application starts. Copy the below Bootstrap class. It will add many People, with many associated addresses to the mongoDB instance, next time you run the application.
import org.keaplogik.Person
import org.keaplogik.Address

class BootStrap {

    def init = { servletContext ->
        if (!Person.count()) {
            def johnDoe = new Person( firstName: "John", lastName: "Doe" ).save(failOnError: true)
            def joeReed = new Person( firstName: "Joe", lastName: "Reed" ).save(failOnError: true)
            def jimSmith = new Person( firstName: "Jim", lastName: "Smith" ).save(failOnError: true)
            def patrickHartwin = new Person( firstName: "Patrick", lastName: "Hartwin" ).save(failOnError: true)
            def steveGunther = new Person( firstName: "Steve", lastName: "Gunther" ).save(failOnError: true)
            def samWhiting = new Person( firstName: "Sam", lastName: "Whiting" ).save(failOnError: true)
            def sarahMathews = new Person( firstName: "Sarah", lastName: "Mathews" ).save(failOnError: true)
            def lisaPudock = new Person( firstName: "Lisa", lastName: "Pudock" ).save(failOnError: true)
            def karaWhiting = new Person( firstName: "Kara", lastName: "Whiting" ).save(failOnError: true)

            johnDoe.addToAddresses(
                    new Address(state: "NY", city: "Windsor", streetAddress: "117 W 2nd St", zipCode: "13865")
            ).addToAddresses(
                    new Address(state: "TX", city: "Alberta", streetAddress: "117 W 2nd St", zipCode: "55555")
            ).addToAddresses(
                    new Address(state: "NY", city: "Longely", streetAddress: "2 Sandy Creek", zipCode: "34009")
            ).addToAddresses(
                    new Address(state: "ME", city: "Ladly", streetAddress: "117 W 2nd St", zipCode: "55533")
            ).addToAddresses(
                    new Address(state: "KY", city: "Korba", streetAddress: "3 Apple St", zipCode: "40351")
            ).save(failOnError: true)

            joeReed.addToAddresses(
                    new Address(state: "KY", city: "Frankfort", streetAddress: "33 Main St", zipCode: "77625")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "71 Kind Ave Apt 3", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "PA", city: "Scranton", streetAddress: "8559 Hard Rock", zipCode: "44567")
            ).addToAddresses(
                    new Address(state: "WV", city: "Charleston", streetAddress: "8233 Juniper Rd", zipCode: "33982")
            ).save(failOnError: true)

            jimSmith.addToAddresses(
                    new Address(state: "PA", city: "Blue Ridge", streetAddress: "780 Country Rd", zipCode: "44564")
            ).addToAddresses(
                    new Address(state: "TX", city: "Ft. Worth", streetAddress: "55 Holdem Dr." , zipCode: "77298")
            ).save(failOnError: true)

            patrickHartwin.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            steveGunther.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "53 Sinking Dr." , zipCode: "98765")
            ).addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "759 Sinking Dr." , zipCode: "98765")
            ).save(failOnError: true)

            samWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sacramento", streetAddress: "1 Beach Rd", zipCode: "98765")
            ).save(failOnError: true)

            sarahMathews.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            lisaPudock.addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "81 Lake Dr.", zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "VT", city: "Burlington", streetAddress: "40 Shorten Ave Apt 33" , zipCode: "22183")
            ).addToAddresses(
                    new Address(state: "NY", city: "Plattsburgh", streetAddress: "1772 Lovely Lane" , zipCode: "22795")
            ).save(failOnError: true)

            karaWhiting.addToAddresses(
                    new Address(state: "CA", city: "Sandiego", streetAddress: "9901 Shore Dr.", zipCode: "98741")
            ).save(failOnError: true)
        }
    }
    def destroy = {
    }
}
Finally, in IntelliJ click the run button. or from console mvn grails:run-app
Your application will be running on localhost. Play around with the default scaffolding UI and the validation.
back to top

Advanced Application Features

Enhanced UI Design

When building a user interface, it is important to first choose a style library. For this example we will be using bootstrap css and js to quickly enhance the user interface of our application. I also would like to be able to customize the default theme, so we will be adding the lesscss-resources plugin as well.

1. Configure Twitter Bootstrap

Head over to the Twitter Bootstrap Github Page and click on download the latest release in the Quick start section of the doc's.
  • Extract the files
  • Copy the bootstrap .js files into your project at web-app/js/bootstrap
  • Copy the bootstrap .less files into your project at web-app/less/bootstrap
  • Copy the /img/~.png files over to your projects web-app/images directory

2. Configure Bootstrap with Less compiler

First, add the new dependency lesscss-resources to your POM.
    <dependency>
        <groupId>org.grails.plugins</groupId>
        <artifactId>lesscss-resources</artifactId>
        <version>1.3.0.3</version>
        <scope>runtime</scope>
        <type>zip</type>
    </dependency>
In the web-app/less directory add a file custom-bootstrap.less. Place an import tag at the top of the page to import the bootstrap less file. Include an override of the iconSpritePath's to point to your images directory.
@import "bootstrap/bootstrap.less";

// Sprite icons path
// -------------------------
@iconSpritePath:          "../images/glyphicons-halflings.png";
@iconWhiteSpritePath:     "../images/glyphicons-halflings-white.png";
This file will be used to override bootstraps UI properties in the future. This way we can do some customization of the default UI theme.

Finally, open the grails Config class and add the following lines to the bottom of the page.
Note: After looking back, this code belongs in AplicationResources.groovy config file. Here is a link to the AplicationResources.groovy
grails.resources.modules = {

    bootstrap {
        resource url:'less/custom-bootstrap.less',attrs:[rel: "stylesheet/less", type:'css']
        dependsOn 'jquery'
    }

}
Earlier we imported the lesscss-resources plugin. This integrates with bootstrap and will compile the bootstrap.less files into css. Now, custom-bootstrap.less compiles on top of the provided bootstrap.less to allow overriding bootstrap variables used to generate stylings. The variables that can be overridden are found on the official github site.(https://github.com/twitter/bootstrap/blob/v2.0.2/less/variables.less)
To use bootstrap in any gsp page, add the tag <r:require modules="bootstrap"/> in the head.

3. Adding a custom controller

Now, we're going to modify our PersonController. Edit to look like so:
class PersonController {

    def scaffold = Person

    def index = {
        def people = Person.list([sort:"lastName", order:"asc"])

        return [people: people]
    }
}
Next we need to create our first Groovy Server Page. To do this we create a file at the location myapp/grails-app/views/person/index.gsp
Add the following code to the groovy server page.
<@ page contentType="text/html;charset=UTF-8" >
<@ page contentType="text/html;charset=UTF-8" >
<html xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
    <head>
        <meta name="layout" content="main"/>
        <r:require modules="bootstrap"/>
    </head>
    <body>
        <section>
            <div class="container">
                <div class="row">
                    <header class="page-header">
                        <h3>Person <small class="lead">Address List</small></h3>
                    </header>
                    <div class="span3">
                        <g:link class="btn btn-block btn-link" action="create">
                            Create New Person
                        </g:link>
                        <div class="well">
                            <ul class="nav nav-list">
                                <li class="nav-header">People</li>
                                <li class="active">
                                    <a id="view-all" href="#">
                                        <i class="icon-chevron-right pull-right"></i>
                                        <b>View All</b>
                                    </a>
                                </li>
                            <g:each in="${ people }" var="person" status="i">
                                <li>
                                    <a href="#Person-${person.id}">
                                        <i class="icon-chevron-right pull-right"></i>
                                        ${ "${ person.firstName } ${ person.lastName }" }
                                    </a>
                                </li>
                            </g:each>
                            </ul>
                        </div>
                    </div>
                    <div class="span9">
                    <g:each in="${ people }" var="person" status="i">
                        <div id="Person-${ person.id }" class="well well-small">
                            <table class="table table-bordered table-striped">
                                <caption>
                                    ${ "${ person.firstName } ${ person.lastName }" }: List of known addresses
                                </caption>
                                <thead>
                                    <tr>
                                        <th>State</th>
                                        <th>City</th>
                                        <th>Street</th>
                                        <th>Zip Code</th>
                                        <th>Options</th>
                                    </tr>
                                </thead>
                                <tbody>
                                <g:each in="${ person.addresses }" var="address">
                                    <tr>
                                        <td>${ address.state }</td>
                                        <td>${ address.city }</td>
                                        <td>${ address.streetAddress }</td>
                                        <td>${ address.zipCode }</td>
                                        <td><g:link class="btn btn-small btn-inverse" controller="address"
                                                    action="edit" id="${address.id}">
                                                <i class="icon-edit icon-white"></i>
                                            </g:link>
                                        </td>
                                    </tr>
                                </g:each>
                                </tbody>
                            </table>
                            <div class="btn-group">
                                <g:link class="btn btn-primary" action="edit" id="${person.id}">
                                    <i class="icon-edit icon-white"></i>Edit
                                </g:link>
                            </div>
                        </div>
                    </g:each>
                    </div>
                </div>
            </div>
        </section>
        <g:javascript>
            $('ul.nav > li > a').click(function(e){
                if($(this).attr('id') == "view-all"){
                    $('div[id*="Person-"]').fadeIn('fast');
                }else{
                    var aRef = $(this);
                    var tablesToHide = $('div[id*="Person-"]:visible').length > 1
                            ? $('div[id*="Person-"]:visible') : $($('.nav > li[class="active"] > a').attr('href'));

                    tablesToHide.hide();
                    $(aRef.attr('href')).fadeIn('fast');
                }
                $('.nav > li[class="active"]').removeClass('active');
                $(this).parent().addClass('active');
            });
        </g:javascript>
    </body>
</html>
***Note: We don't need to define any url mappings for the index. By default groovy will recognize that PersonController.index() maps to views/person/index.gsp.
Next, open the view: views/layouts/main.gsp. Remove any reference links to css files, except the main.css. Ensure that the tag <r:layoutResources /> is declared at both the bottom of the head and body tags. It should look similar to this.
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <title><g:layoutTitle default="Grails Mongo"/></title>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="shortcut icon" href="${resource(dir: 'images', file: 'favicon.ico')}" type="image/x-icon">
        <link rel="apple-touch-icon" href="${resource(dir: 'images', file: 'apple-touch-icon.png')}">
        <link rel="apple-touch-icon" sizes="114x114" href="${resource(dir: 'images', file: 'apple-touch-icon-retina.png')}">
        <link rel="stylesheet" href="${resource(dir: 'css', file: 'main.css')}" type="text/css">
        <g:layoutHead/>
        <r:layoutResources />
    </head>
    <body>
        <g:layoutBody/>
        <div id="spinner" class="spinner" style="display:none;"><g:message code="spinner.alt" default="Loading&hellip;"/></div>
        <g:javascript library="application"/>
        <r:layoutResources />
    </body>
</html>

Also, edit the main.css file. Remove all css except for the section's commented /* PAGINATION */ and /* MESSAGES AND ERRORS */
Finally, go into the Configuration Class: URLMappings and modify as such:
class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(controller: "person", action: "index")
        "500"(view:'/error')
    }
}
Note: The grails runtime supports hot swapping of gsp files except for the template files. It seems to work with URLMappings, but I haven't tested what classes are supported and aren't.

4. Customize Scaffold Templates

We are going to customize the default scaffolding template. This will allow us to keep using scaffolding for our basic CRUD operations.
In console run: grails install-template
Copies the the templates used by Grails during code generation to your project directory at src/templates
  1. Add the tag <r:require modules="bootstrap"/> below <meta name="layout" content="main"> in each of the views: show, list, create, edit
  2. Wrap each of the views with a <div class="container"></div> just inside the body
  3. Modify the nav classes. Wrap the Navigation list with a <div class="navbar"></div>
    • Change the div wrapping the list <div role="navigation"> to <div class="nav">
    • Add the class nav to the ul tag like so: <ul class="nav">
  4. Modify edit, show, create
    • Modify the tag: <fieldset class="buttons"> to <fieldset class="form-actions">
    • In show.gsp replace all class names "property-value" with "uneditable-input"
    • Change this span in show.gsp <span id="${p.name}-label"...> to a label tag.
  5. Modify list.gsp
    • The table class should look as follows: <table class="table table-bordered table-striped">
Feel free to play around with these views more. While, I'm leaving the instructions simple, it is likely you will want to clean up the template views a little more.

5. Improve Scaffold Output

One thing we would like to do is add proper error messages to CRUD forms. For each validation we can define an error message in the grails-app/i8n/messages.properties file. This file can map validation errors to their associated messages. Insert these lines at the bottom of this file.
person.firstName.blank=first name cannot be left blank
person.lastName.blank=last name cannot be left blank

address.streetAddress.blank=Street Address cannot be left blank
address.city.blank=City cannot be left blank
address.zipCode.blank=Zipcode cannot be left blank
address.zipCode.validator=Zipcode must be a valid number
address.zipCode.size=Zipcode must be a valid 5 digit number
address.state.blank=State cannot be left blank
address.state.size=State must be entered as a valid state code. Ex: NY
Next, we want to display the Person when displaying an Address, and display the Addresses when displaying a person. To do this, we override the toString method of each domain class.
So in Person
    String toString(){ return "${firstName} ${lastName}"}
And in Address
    String toString(){ return "${streetAddress} ${city}, ${state}, ${zipCode}"}
The application is now setup on bootstrap. Go ahead and run the application!
back to top

Adding Security

If you would like to follow along but haven't walked through each tutorial, downoad this tag on github. This is a snapshot of the demo project before adding security. To clone from git, use these commmand:
    $ git clone https://github.com/keaplogik/grails-mongodb-demo.git
    $ cd grails-mongodb-demo/
    $ git checkout v1.0

1. Configuring Spring Security Plugin

To start, ensure you have the spring-security-core plugin installed. If not, add it to your POM if using maven, otherwise add it to the dependency list of the BuildConfig class. If following along, with the grails-mongodb-demo, you will already have it installed.
A useful command s2-quickstart is built into the security plugin. It generates the controllers to handle authentication, as well the domain classes you'll need to store user information.
As in the springsource blog, we will create domain classes with names: SecUser and SecRole.
Also generated:
  • SecUserSecRole - links users to roles.
  • LoginController
  • LogoutController
  • Associated login/logout views
So start by runnning:
    $ grails s2-quickstart org.mycompany SecUser SecRole
The application now needs to specify mappings to the login/logout controllers. Add the following to the configuration class UrlMappings:
    "/login/$action?"(controller: "login")
    "/logout/$action?"(controller: "logout")
There are three ways you can configure security.
You can take a controller-centric approach and annotate the actions; work with static URL rules in Config.groovy; or define runtime rules in the database using request maps. Consult the springsource blog post Simplified Spring Security with Grails for more information.

2. Adding static URL maps

For the grails-mongodb-demo, we will secure the application using static URL rules.
The Rules:
  1. Any authenticated user has access to the Person Index
  2. Only Admin's can create/update/delete Persons or Addresses
To enable, set the securityConfigType and add interceptUrlMap Configurations to the Config.groovy class:
grails.plugins.springsecurity.securityConfigType = 'InterceptUrlMap'
grails.plugins.springsecurity.interceptUrlMap = [
        '/person/index':    ['ROLE_USER, ROLE_ADMIN, IS_AUTHENTICATED_FULLY'],
        '/person/**':       ['ROLE_ADMIN'],
        '/address/**':      ['ROLE_ADMIN'],
        '/js/**':           ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/css/**':          ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/images/**':       ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/*':               ['IS_AUTHENTICATED_FULLY'],
        '/login/**':        ['IS_AUTHENTICATED_ANONYMOUSLY'],
        '/logout/**':       ['IS_AUTHENTICATED_ANONYMOUSLY']

]
Optional security configurations:
grails.plugins.springsecurity.password.algorithm='SHA-512'      //pw encryption algorithm 
grails.plugins.springsecurity.portMapper.httpPort = "8080"      //port map for http
grails.plugins.springsecurity.portMapper.httpsPort = "8443"     //port map for https
grails.plugins.springsecurity.rejectIfNoRule = true             //force authentication if no rule exists

3. Bootstrap in security data

Our application needs to determine who has access to what URL. Because of this, we need to create a set of rules. They only need to be created once on application start, so lets use Bootstrap to create them.
class BootStrap {
    def init = {
        ...
        def userRole = SecRole.findByAuthority('ROLE_USER') ?: new SecRole(authority: 'ROLE_USER').save(failOnError: true)
        def adminRole = SecRole.findByAuthority('ROLE_ADMIN') ?: new SecRole(authority: 'ROLE_ADMIN').save(failOnError: true)
        ...
    }
}
Now configure two users; one with admin privilege and, one with default user privilege.
  • Admin User {u/p}: {admin/admin}
  • Default User {u/p}: {guest/guest}
In the Bootstrap.init{...}, add persistence for the default users :
    //add an admin and default user
    def adminUser = SecUser.findByUsername('admin') ?: new SecUser(
            username: 'admin',
            password: 'admin',
            enabled: true).save(failOnError: true)

    def basicUser = SecUser.findByUsername('guest') ?: new SecUser(
            username: 'guest',
            password: 'guest',                          //pw encoded by security plugin
            enabled: true).save(failOnError: true)

    if (!adminUser.authorities.contains(adminRole)) {
        SecUserSecRole.create adminUser, adminRole
    }
    if (!basicUser.authorities.contains(userRole)) {
        SecUserSecRole.create basicUser, userRole
    }

4. SecurityTagLib - Conditionally display gsp content

The security tags are part of the sec namespace, and can be used in your gsp's. Heres an example:
<sec:ifLoggedIn>
    <p>Your Logged in!</p>
</sec:ifLoggedIn>
We are going to use the security tag lib, to hide edit/create options on the person/index page. Specifically the condition will check if a user has the role: ROLE_ADMIN:
<sec:ifAllGranted roles="ROLE_ADMIN">hide this stuff</sec:ifAllGranted>
To view a list of tags and their explanations see the Grails SecurityTagLib Documentation.
Go ahead and open up the view grails-mongodb-demo/grails-app/views/person/index.gsp.
Wrap the Create New Person link like so:
<sec:ifAllGranted roles="ROLE_ADMIN">
<g:link class="btn btn-block btn-link" action="create">
    Create New Person
</g:link>
</sec:ifAllGranted>
And now the table data containing the options column, along with the button group:
<sec:ifAllGranted roles="ROLE_ADMIN">
<th>Options</th>
</sec:ifAllGranted>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<td><g:link class="btn btn-small btn-inverse" controller="address"
            action="edit" id="${address.id}">
        <i class="icon-edit icon-white"></i>
    </g:link>
</td>

...

<sec:ifAllGranted roles="ROLE_ADMIN">
<div class="btn-group">
    <g:link class="btn btn-primary" action="edit" id="${person.id}">
        <i class="icon-edit icon-white"></i>Edit
    </g:link>
</div>
</sec:ifAllGranted>
</sec:ifAllGranted>

5. Configure Security Pages with Bootstrap UI

We want to give the user an option to log out from any screen. Let's use a static navbar in our /layouts/main.gsp:
<body>
    <div class="navbar navbar-static-top">
        <div class="navbar-inner">
            <div class="container">
                <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </a>
                <g:link class="brand" uri="/">Address Tracker</g:link>
                <div class="nav-collapse">
                    <ul class="nav pull-right">
                        <sec:ifLoggedIn>
                        <li>
                            <g:link controller="logout" action="index">Logout</g:link>
                        </li>
                        </sec:ifLoggedIn>
                    </ul>
                </div>
            </div>
        </div>
    </div>
    <g:layoutBody/>
    ...
My auth.gsp page configured with twitter bootstrap is below, but you can modify yours however.
<html>
<head>
    <meta name='layout' content='main'/>
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.login.title"/></title>
</head>
<body>
    <div class='container'>
            <g:if test='${flash.message}'>
                <div class='alert alert-error'>${flash.message}</div>
            </g:if>
            <div class="row span12">
                <legend><g:message code="springSecurity.login.header"/></legend>
                <div class="span6">
                    <form action='${postUrl}' method='POST' id='loginForm' class='form-horizontal' autocomplete='off'>
                        <div class="control-group">
                            <label class="control-label" for='username'><g:message code="springSecurity.login.username.label"/>:</label>
                            <div class="controls">
                                <input type='text' class='text_' name='j_username' id='username'/>
                            </div>
                        </div>
                        <div class="control-group">
                            <label class="control-label" for='password'><g:message code="springSecurity.login.password.label"/>:</label>
                            <div class="controls">
                                <input type='password' class='text_' name='j_password' id='password'/>
                            </div>
                        </div>
                        <div class="control-group" id="remember_me_holder">
                            <div class="controls">
                                <label class="checkbox" for='remember_me'><g:message code="springSecurity.login.remember.me.label"/>
                                    <input type='checkbox' name='${rememberMeParameter}' id='remember_me'
                                           <g:if test='${hasCookie}'>checked='checked'</g:if>/>
                                </label>
                                <input class="btn btn-primary" type='submit' id="submit" value='${message(code: "springSecurity.login.button")}'/>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="span4">
                    <div class="">
                        <dl class="dl-horizontal">
                            <dt>Admin User (u/p):</dt>
                            <dd>admin/admin</dd>
                        </dl>
                        <dl class="dl-horizontal">
                            <dt>Guest User (u/p):</dt>
                            <dd>guest/guest</dd>
                        </dl>
                    </div>
                </div>
            </div>
    </div>
</body>
</html>

And the denied.gsp:
<head>
    <meta name='layout' content='main' />
    <r:require modules="bootstrap"/>
    <title><g:message code="springSecurity.denied.title" /></title>
</head>

<body>
    <div class='container'>
        <div class='alert alert-block alert-error'>
            <g:message code="springSecurity.denied.message" />
            <br/><br/>
            <g:link class="btn btn-inverse" uri="/">Return Home</g:link>
        </div>
    </div>
</body>

The application is now setup with basic spring security. You can find a list of additional plugins with advanced configurations, on the spring security plugin page
back to top

Comments

  1. This is great stuff, Billy. Thank you!

    I found your post because I was trying to see how people were integrating Grails and Twitter Bootstrap. When I saw your article I expected to see reference to the Twitter Bootstrap Plugin for Grails (http://grails.org/plugin/twitter-bootstrap).

    Have you tried that route and found yours superior? I am beginning a new project myself and want to chose the best way to integrate Grails and Bootstrap.

    Thank you again!

    ReplyDelete
  2. Yes, I did check out the twitter-bootstrap plugin. The main issue is that it becomes difficult to customize bootstrap if you need. In this example, you can easily customize bootstrap by modifying bootstraps variables (link colors, background color, etc..) by adding overrides in your customize-bootstrap.less file. This is an issue with the bootstrap plugin. https://github.com/groovydev/twitter-bootstrap-grails-plugin/issues/9. Also you can add in additional less files to create your own template. Thing's like font-awesome or chosen for bootstrap. And finally, you are not bound to the bootstrap version that the plugin supports. This is a simple route to take. Another neet thing about the lesscssresource plugin, is that you can modify your less file, and changes take affect immediately on dev environment.

    ReplyDelete
  3. That makes perfect sense. I'm going to go your route. Thank you! :^)

    ReplyDelete
  4. Quick comment:

    $ grails integrate-with --intelliJ

    should be

    $ grails integrate-with --intellij (lowercase "j")

    ReplyDelete
  5. Thanks, I made the adjustment. Also added a not to the "Building the Domain Model" section. With mongo, your better off not using the field "hasMany". Instead use a list of addresses within your Person domain class. Unfortunately, scaffold views do not support this, so for this demo stick with using the relational syntax. It is much more efficient to embed the address list into your Person collection items. I've found that queries are extremely slow when querying on relationships in Mongo.

    ReplyDelete
  6. Hi Billy, just checking, your tutorial doesn't use any of the javascript files from bootstrap, right? Is there anything specific, like bundling those into a resource (e.g., AppResources.groovy), that we need to do before using the JS files on the front-end. I am just curious to know if there is a 'recommended' approach.

    ReplyDelete
  7. Ok, after looking back at this, the less resource should probably have been declared in the ApplicationResource.groovy file. If you would like to include the twitter bootstrap javascript files, that would be the best place to add them. I have added an example ApplicationResource file you can use in place of the added bootstrap module in config.groovy: https://gist.github.com/4477107

    Also, you may want to separate out the javascript files into different modules. This way you only include the javascript that's necessary for the current page your working on. Say say you are using the popover.js library. In Application resources you would declare modules like so:

    bootstrapTooltip {
    resource url:'js/bootstrap/bootstrap-tooltip.js'
    }

    bootstrapPopover {
    dependsOn 'bootstrapTooltip'
    resource url:'js/bootstrap/bootstrap-popover.js'
    }

    In order to use the popover js, you have to import the tooltip js as well according the the tw bootstrap specs. That is why we use the dependsOn indicator above. Now inside your view.gsp, add this tag to your head:

    That should import the javascript files necessary at the bottom of your generated page.

    Hope this helps.

    ReplyDelete
  8. Correction to this line of last comment: *add this tag to your head: " The tag didn't appear in my comment. r:require modules="bootstrapPopover"

    ReplyDelete
  9. Thanks Billy, this makes complete sense.

    For anyone else who is interested and who's also not using the twitter-bootstrap plugin, I'll suggest at least taking a look at the plugin's BootstrapResources.groovy file. It does a real fine job of enumerating and declaring all the sub-modules.

    ReplyDelete
  10. grains-grains-helpApril 25, 2013 at 5:23 PM

    mvn compile command throws an error

    c:\dev\workspace\grails-mongodb-demo> mvn compile
    'mvn' is not recognized as an internal or external command,
    operable program or batch file.

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. I am getting the following error. Please help me out!!

    Error |
    2013-04-29 11:01:54,010 [main] ERROR context.GrailsContextLoader - Error initia
    lizing Grails: Error creating bean with name 'instanceTagLibraryApi': Injection
    of autowired dependencies failed; nested exception is org.springframework.beans.
    factory.BeanCreationException: Could not autowire method: public void org.codeha
    us.groovy.grails.plugins.web.api.TagLibraryApi.setGspTagLibraryLookup(org.codeha
    us.groovy.grails.web.pages.TagLibraryLookup); nested exception is org.springfram
    ework.beans.factory.BeanCreationException: Error creating bean with name 'gspTag
    LibraryLookup': Invocation of init method failed; nested exception is org.spring
    framework.beans.factory.BeanCreationException: Error creating bean with name 'or
    g.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib': Initialization o
    f bean failed; nested exception is org.springframework.beans.factory.BeanCreatio
    nException: Error creating bean with name 'grailsUrlMappingsHolder': Cannot reso
    lve reference to bean 'urlMappingsTargetSource' while setting bean property 'tar
    getSource'; nested exception is org.springframework.beans.factory.BeanCreationEx
    ception: Error creating bean with name 'urlMappingsTargetSource': Cannot resolve
    reference to bean 'org.grails.internal.URL_MAPPINGS_HOLDER' while setting const
    ructor argument; nested exception is org.springframework.beans.factory.BeanCreat
    ionException: Error creating bean with name 'org.grails.internal.URL_MAPPINGS_HO
    LDER': Invocation of init method failed; nested exception is java.lang.NoSuchMet
    hodError: com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap$Builder
    .maximumWeightedCapacity(J)Lcom/googlecode/concurrentlinkedhashmap/ConcurrentLin
    kedHashMap$Builder;

    ReplyDelete
    Replies
    1. Were you able to resolve this? I need to update the project to the latest grails version and update mongo. Those may resolve the issue. Do a pull on the repo later today and try again.

      Delete
  13. I had some problems running the application after entrying the dummy data (not because of the dummy data). After updating mongoDB dependency problem was resolved:


    org.grails.plugins
    mongodb
    1.2.0
    runtime
    zip

    ReplyDelete
    Replies
    1. I updated the source on github to use the latest stable mongo gorm client (1.2.0). Also updated to Grails 2.2.0. I'm curious why you set the build scope to runtime? In the specs it states "compile" http://grails.org/plugin/mongodb

      Delete
    2. Maybe I'm mistaken or I misunderstood something but this tutorial states:


      org.grails.plugins
      mongodb
      1.0.0.GA
      runtime
      zip


      Delete
    3. Ah I see what you are saying. I will make the adjustments in the article.

      Delete
  14. When running the application after doing all the plugin configuration steps and before "Building an Application" section I notice that I still see tomcat installed under Installed Plugins on the left side of the browser. Should that be saying Jetty? Also, I feel like the welcome page should be different if running jetty should it not?

    ReplyDelete
    Replies
    1. I solved it...had to run this:
      grails uninstall-plugin tomcat
      grails refresh-dependencies

      i'm using intellij with auto-import turned on and the auto import doesnt seem to be working.

      Delete
  15. Hi:

    I am gettigng the following error, right after I configure everyhitng in pom.xml and try to do a maven clean compile from eclipse. (right before I get into "Building an app" section) -
    symbol : method ultimateTargetClass(groovy.lang.GroovyObject)
    location: class org.springframework.aop.framework.AopProxyUtils
    Method method = ReflectionUtils.findMethod(AopProxyUtils.ultimateTargetClass(controller),

    Please help
    Thanks

    ReplyDelete
    Replies
    1. I have not set this up in eclipse. Have you tried compiling from command line? Also, what version of grails you have installed may have an effect. The latest version uses 2.2.0. I am not familiar with the error you are seeing.

      Delete
  16. Nice to see another IntelliJ based Grails developer. Rock on!

    ReplyDelete
  17. Nice tutorial for the most part.. My only suggestion is loose the Maven portion. After all, this is about learning Grails and you yourself would not use it. After all, this about learning Grails and that's what I want to do.

    If you feel it's worth it, make a separate Maven specific tutorial.

    Otherwise, I like the mix of things you've got here.

    ReplyDelete
    Replies
    1. Thanks for your response. I gave it some thought and agree. I will leave the maven integration in the tutorial but comment about how it's been removed and how to do it without maven. I merged the git project with a fork someone had created and removed maven. Thanks again.

      Delete
  18. its nice i really like to understand and fallow

    ReplyDelete
  19. Hi Billy,
    Im a beginner with Grails ( have exp. in java though)

    I need to extract data records from a table and display in tabular view using groovy-grails.
    Please please help me out with to 'get started'

    ReplyDelete
  20. Really good tutorial..Thanks a lot.

    ReplyDelete

Post a Comment

Popular posts from this blog

Spring HATEOAS - Embedded Resources with HAL

Atmosphere Websockets & Comet with Spring MVC

Microservices Tech Stack with Spring and Vert.X