Monday, March 8, 2010

Grails Portal Part I

This article is the first in a series of three that describes a simple way to build a working framework for a user portal. This framework is built upon some very popular features in Grails and provides many examples of how to integrate popular plug-ins such as JSecurity. This article describes concepts that a novice Grails developer may find challenging.

In the first article, a step-by-step approach will show how to construct the architecture and creating a skeleton set of pages that will be used in articles two and three. By the end of this article the reader will be familiar with using basic Grails concepts to form the basis of a Grails-based portal application. Not all of the classes referenced in the code examples in the text of this article have been included.
Why a Grails Portal?

I have spent many years creating web applications that allow users to authenticate, and then be permitted access to specific features. This does not imply that I have not used formal “portal” technologies, but most small web applications don’t merit being created within a jsr-168/286 portal environment. Typically, most developers need a quick way to allow users to self-register and authenticate, and then manage the authorization of the user to specific functionality.

Grails poses a very interesting choice for these kinds of applications, first because of its convention- based development model and second, its ability to incorporate new functionality quickly. Basic features of Grails such as GSP templates, Grails Object Relational Mapping (GORM), and Web-Flow enable portal functionality to be quickly implemented.
What can a Grails Portal do?

A Grails portal is the fundamental user-based set of features that are used to write interactive web applications (apps that require users to log in and then provide individualized functionality.) A Grails portal can be a launching pad for implementing a web framework that creates and implementation that differs based on role and user. In this article, I will demonstrate how to use Grails to create a simple portal for the fictional wellness company “Wellness Unlimited.” The portal will execute wizards to help users schedule appointments, report health issues, permit self-registration, receive specialized content through email, and allow them to sign up for web-based services.

To accomplish this, our web application combines the user security features of JSecurity with a custom database schema that extends the simple database model provided by JSecurity. Our application also extends functionality to manage users. These customizations are combined with default scaffolding enabled through Grails Web Flow and GORM.
What you will need to do the examples for this article

1. A downloaded and working Grails environment

2. A database source such as MySQL
Grails Portal Project definition

The classes that are used to describe the Wellness Unlimited functionality are an example of a generic web application. The abstractions used are applicable for most web applications that involve common business processes such as recording transactions, scheduling and purchasing. They can also be used in other non-economic functions such as list and catalog management. In these business domains, the organization presents website data to the user and then allows user interaction and records the selections made by the user. Once the user has entered the information, an administrator or business user should be presented with the data for further processing. In order to do this in Grails, domain classes, a framework like GORM, and a view technology like GSP are necessary.

I have written many web applications that use the Spring and Hibernate frameworks and GORM is definitely superior from a usability perspective in getting database-backed web applications to work quickly and properly. The only problem is that if these tools are not configured correctly, they will create bloated applications with inefficient database queries generated by the web application. To prevent this from happening, I have adopted a few practices that facilitate the database and domain-class approach taken in this project.

1. Keep it simple

2. Don’t be afraid to “flatten” the database if it makes it easier; i.e. a generic database is not always best.

3. Use the options in GORM if possible before writing code.

The primary domain objects used to define the application domain for the Wellness Unlimited portal are Party, Order, Product, and RegistrationEvent. A brief description of each follows.
Party

A Party signifies a specific individual that takes part in the system, but a party is not necessarily only a person. It could be a system, individual user, or anything that can be identified by the fields First Name and Last Name. These fields are the mandatory fields on the equivalent domain class. The Party class holds all of the information that composes an individual that participates in the functionality of the portal.
Order

The Order encapsulates the concept of orders and fulfillment. This abstraction is important to the implementation of any selection for action from the website. Orders are executed to establish that something needs to happen. Order line items are created to track the specific products that are selected by the user.
Product

Products exemplify the concept of any item that can be selected by a user from Wellness Unlimited. An example of a product would be something generic like a massage; a product could also be an online user request for a specific type of acupuncture treatment. Products have line items that enable bundling of products into a larger product. An example of a bundled product would be to combine a facial with a pedicure into one item that a user schedules on the Wellness Unlimited website.
Event

Events describe the concept of an occurrence of a business “event.” The recording of different types of business events enable business processes to be recorded along with all of the information associated with the process. The implementation of this portal uses Grails WebFlow to initiate business events and track the progress of these events. Typical events could be:

1. Scheduling of a service such as a massage

2. Purchasing a product

3. Requesting a call back/email for help with a wellness issue
How to create a Grails Portal

The steps are as follow:

1. Create the Grails project

2. Install and configure the JSecurity Plugin

3. Configure the domain classes and GORM

4. Configure the User Management Views
Create the Grails project

The first step in setting up this project is to create a Grails project using grails create-project grailsPortal. This command creates the typical directory structure associated with all Grails projects.
Install and configure the JSecurity Plugin

The second step is to install the JSecurity plugin. The command is: “grails install-plugin jsecurity.” In addition, JSecurity installs the quick-start script to generate the basic user screen, controllers, and domain classes for JSecurity. The next step installs the basic setup for JSecurity. The command “grails quick-start” creates an authController, a view called login.gsp, and six domain classes:

· JsecPermission: holds the permissions and the descriptions of the possible actions that can be applied to the permission

· JsecRole: holds the name of the Role

· JsecRolePermissionRel: holds the relationship between the Role and Permission

· JsecUser: holds the user information (in the beginning only username and password)

· JsecUserPermissionRel: the relationship between the user and permission

· JsecUserRoleRel: the relationship between the JsecUser and the Role

JSecurity is an excellent Grails plugin. It is a generic framework for web application security that is easily extended. At this point the application will work if you use “grails run-app.” It gives you a great starting point but does not finish the work for a user-authenticated application such as a portal.

JSecurity accomplishes user authentication by looking up the user in the JsecUser table and the relationships that the user has in JsecUserRolRel. For this project we will create two role records: one that describes an administration role, and another that describes a user role. In addition we will create two user records: one that describes an administration user (admin); and another that describes a user for the application (guest).

The two user records are associated with the roles by using the JsecUserRoleRel table. As shown in Figure 1, the relationship enables roles to be applied to users.

Figure 1: Party and JSecurity tables ERD

In Part I of this article we will detail how to augment the authorization controller (authController.groovy) to use the “Admin” and “User” role. In the “Creating Default Data” section of this article (part II), I will show how automatically create default roles and users as well as the relationship table when the application is started.

This project augments the default domain classes provided by JSecurity. The default classes are not enough because there needs to be a relationship between these classes and the classes that describe the rest of the application. We accomplish this by adding a reference to the classes that describe the user from a “Wellness Unlimited” point of view. This can be seen in Figure 1 in the JSEC_USER table.
Configure the domain classes and GORM

The Party class is contained in Listing 1. The Party domain class references other domain classes by default such as ContactPhone, ContactEmail, ContactAddress and PartyType. These references establish direct relationships in the database and make it much easier to use the classes at run time. Also, you should notice that there is a one-to-many relationship with the OrderRecord domain class.

class Party implements Serializable{

static mapping = {

orderRecordList column:'partys_id',joinTable:'party_order_record'

partyType column:'party_type_id'

}

String firstName

String middleName

String lastName

String prefix

String suffix

Date birthDate

ContactAddress homeAddress

ContactAddress workAddress

ContactPhone homePhone

ContactPhone workPhone

ContactPhone cellPhone

ContactEmail workEmail

ContactEmail homeEmail

PartyType partyType

static hasMany = [ orderRecordList : OrderRecord ]

static constraints = {

prefix(size:1..20, blank:true,nullable:true)

firstName(size: 1..100, blank: false,nullable:false)

middleName(size:1..50, blank:true,nullable:true)

lastName(size: 1..100, blank: false,nullable:false)

suffix(size:1..20, blank:true,nullable:true)

birthDate(nullable:true)

partyType(nullable:true)

homeAddress(nullable:true)

workAddress(nullable:true)

homePhone(nullable:true)

workPhone(nullable:true)

cellPhone(nullable:true)

workEmail(nullable:true)

homeEmail(nullable:true)

orderRecordList(nullable:true)

}

String toString() {

return firstName+" "+lastName

}

}

Listing 1: Party Domain class



Listing 1 shows that a Party can have many orders. The syntax indicates to Grails GORM that an intermediate table will need to be created that will contain a reference to both the OrderRecord (Listing 2) class and to the Party class. Grails has really improved support recently for many-to-many relationships and one-to-many relationships when generating scaffolding. Our example application does not use many cases of many-to-many relationships, but as support improves it will make it even easier to create more generic systems.

Figure 2: OrderRecord and Inventory Entity Relationship Diagram

The OrderRecord class is contained in Listing 2. This class is the main reference point for when an order is created; the OrderRecordLineItem class (which is in the zip file; see the “Learn more” section) has a reference to an OrderRecord (Listing 2). The Entity Relationship Diagram that represents this data is shown in Figure 2.

class OrderRecord implements Serializable{

static mapping = {

table 'order_record'

grossAmount column:'gross_amount'

orderComment column:'order_comment'

orderDate column:'order_date'

taxAmount column:'tax_amount'

totalAmount column:'total_amount'

orderStatus column:'order_status'

orderRecordType column:'order_record_type_id'

}

String orderNumber

Double grossAmount

String orderComment

Date orderDate

Double taxAmount

Double totalAmount

String orderStatus

OrderRecordType orderRecordType

Party party

static constraints = {

orderNumber(nullable: false)

grossAmount(nullable: false)

orderRecordType(nullable:false)

party(nullable:false)

orderComment(size: 1..1000, blank: false, nullable:true)

orderDate(nullable: false)

taxAmount(nullable: false)

totalAmount(nullable: false)

orderStatus(size: 0..100)

}

String toString() {

return orderNumber

}

}

Listing 2: OrderRecord class

The OrderRecordLineItem has a reference to the Product which is referenced in Listing 3. This configuration is purposely done so Products can be bundled into an order. (The database design to accomplish this is shown in Figure 2.)



class Product implements Serializable{

static mapping = {

table 'product'

cd column:'cd',index: "product_cd_idx", unique: true

dsc column:'dsc'

ecommerceCode column:'ecommerce_code'

name column:'name'

netCostAmount column:'net_cost_amount'

netSalesAmount column:'net_sales_amount'

productImageuripath column:'product_imageuripath'

salesChannel column:'sales_channel_id'

productType column:'product_type_id'

}

String cd

String dsc

String ecommerceCode

String name

Double netCostAmount

Double netSalesAmount

String productImageuripath

SalesChannel salesChannel

ProductType productType



static constraints = {

cd (size: 1..20, blank: false,nullable:false)

dsc (size: 1..200, blank: true,nullable:true)

ecommerceCode (size: 1..100, blank: false,nullable:false)

name (size: 1..100, blank: false)

netCostAmount (nullable: false)

netSalesAmount (nullable: false)

productImageuripath(size: 1..255, blank: true,nullable:true)

salesChannel (nullable:false)

productType (nullable:false)

}

String toString() {

return name

}

}


Listing 3: Product class



The RegistrationEvent is a class (see Listing 4) where registration transactions happen in the portal. One of the requirements is to be able to register patients for weekly Wellness Unlimited classes; this class enables that functionality.

Another interesting point of the domain classes in this application is the toString() method that is implemented. When classes are generated by Grails scaffolding, by default the classes use this method as a representation of the value of the row in the database.

Constraints that are used in the domain classes will have a direct impact on how default scaffolding generates controllers and views. In this application the blank:true and size:x…x+n are commonly used to control how the fields are generated when they are generated through scaffolding.

Constraints also play a large role in the generation of the database classes. The nullable constraint relates directly back to how the domain class is generated into a table in that it makes the column mandatory (not null). The size constraint gives the indication of how large the field will be in the database. The database design for this are of the database is shown in Figure 3.

Figure 3: Registration Event ERD

Another item to note about all of the domain classes is that they all implement the Serializable interface. This is quite important. One of the main features used in the Wellness Essentials portal are wizards that enable the users to schedule appointments, order products, and go through a web-based workflow; to enable this, Grails WebFlow is used. It assumes that all domain classes have this interface implemented because it will attempt to store the state of the flow independent of the database. If you are not familiar with Serializable, you should reference the Sun Java documentation around java.io.Serializable (see the “Learn More” section for details).

import java.sql.Timestamp

class RegistrationEvent implements Serializable{

Party registrationFor

Date registrationDate

EmergencyContact emergencyContact1

EmergencyContact emergencyContact2

EmergencyContact emergencyContact3

OrderRecord orderRecord

JsecUser registrationUser

RegistrationDoctor registrationDoctor

static constraints = {

registrationFor(nullable:false)

registrationUser(nullable:false)

registrationDate(nullable:true)

emergencyContact1(nullable:true)

emergencyContact2(nullable:true)

emergencyContact3(nullable:true)

orderRecord(nullable:true)

registrationDoctor(nullable:true)

}

static mapping={

emergencyContact1 lazy:false

emergencyContect2 lazy:false

emergencyContact3 lazy:false

orderRecord lazy:false

}

String toString() {

return registrationFor.firstName+" "+registrationFor.lastName

}

}


Listing 4: RegistrationEvent class
Configure the User Management Views

After the domain classes are created you can run the Grails application, but it won’t actually do much. In this step we will configure the views and controllers for managing the user login into the Grails Portal. To implement user management features, we must have the ability to separate users into roles. Luckily, JSecurity enables this functionality. In this application I assume that there are two roles, Admin and User (these roles are created by default in the bootstrap.groovy that is available in the zip file; see the “Learn More” section).

A default authController.groovy is created by the initial installation of the JSecurity plug-in. You will need to replace the controller with the contents of Listing 5. The main difference between the original controller and this one is that the original controller only has the code for logging in and logging out and does not provide any code for inserting a user into the database and assigning a role to them.

The additional closures that I have created are register and registerUser. They handle the registration of new users as well as modifying existing user profile information. Another feature of this controller is that most database handling is done using Grails services (wellUnlSecurityService, contactUtilService) which enable the abstraction of database access and manipulation to these classes.

The corresponding views for this controller handle the modification of the user and registration. These are contained on the zip file. In the next installation of this series I will go over how these were created as well as formulating the User Interface (UI).

import org.jsecurity.authc.AuthenticationException

import org.jsecurity.authc.UsernamePasswordToken

import org.jsecurity.SecurityUtils

import com.wellUnl.exception.ValidationException

class AuthController {

def jsecSecurityManager

def wellUnlSecurityService

def contactUtilService

def index = { redirect(action: 'login', params: params) }



def login = {

return [ username: params.username, rememberMe: (params.rememberMe != null), targetUri: params.targetUri ]

}



def register={

JsecUser ju=new JsecUser()

ju.party=new Party()

String theAction="registerUser"

try{

ju=wellUnlSecurityService.getRegisteredUser()

if (ju!=null){

theAction="updateUser"

}

}catch(Exception e){}

return [authInstance:ju,userAction:theAction]

}

def edit={

def ju=wellUnlSecurityService.getRegisteredUser()

render (model:[authInstance:ju],view:'register')

}

def registerUser={

JsecUser.withSession { session ->



log.info("In registerUser")

Party userParty

JsecUser user

JsecUser ju

boolean isValidationError=false

def userName=params.userName

try{

ju=wellUnlSecurityService.getRegisteredUser()

}catch (Exception e){

log.info("Registering a New User")

ju=null;

}

log.info("checking for mandatory fields")

if (userName==null || userName=="" || params.firstName==null || params.firstName=="" || params.lastName==null || params.lastName=="" ||

params.password==null || params.password=="" || params.passwordVerify==null || params.passwordVerify==""){

log.info("no username, first name, last name, password, or password verify entered")

flash.message="We are sorry, you have to enter a user name, First name, Last Name, Password and Verify Password"

render(model:[authInstance:user],view:'register')

isValidationError=true

}

log.info("checking for existing user")

def foundUser=null

if (ju==null){

foundUser=JsecUser.findByUsername(userName)

if (foundUser!=null){

log.info("Trying to register duplicate user -"+userName)

flash.message="We are sorry this user name is already taken, please pick another"

render(model:[authInstance:user],view:'register')

isValidationError=true

session.clear()

}

}

log.info("checking for password verification")

if (params.password!=params.passwordVerify){

log.info("Trying to register passwords do not match -"+userName)

flash.message="We are sorry your passwords do not match, please try again"

render(model:[authInstance:user],view:'register')

isValidationError=true

}

user=contactUtilService.doUser(ju,params)

log.info("user="+user)

log.info("validating data")

try{

user.party=this.contactUtilService.doParty(user.party,params)

}catch(ValidationException ex){

flash.message = "ERROR-09:There was a problem validating the information, please try again"

log.error(ex.exceptionVal.errors)

render(model:[authInstance:user,authErrorInstance:ex.exceptionVal],view:'register')

session.clear()

return

}

if (!isValidationError){

log.info("about to save the user")

def userRole=JsecRole.findByName("User")

try{

user.party.save()

user.save()

new JsecUserRoleRel(user: user, role: userRole).save()

user.save(flush:true)

log.info("successfully saved the user")

}catch(Exception e){

session.clear()

flash.message="ERROR-11: There was a problem saving the User, please try again"

render(model:[authInstance:user],view:'register')

return

}

if (ju==null){

flash.message="User Created You can log in now"

}else{

flash.message="User Profile Updated"

}

if (user.party.id==null){

log.error("registerUser:can not create the user")

flash.message = "ERROR-10:There was a problem saving the User, please try again"

render(model:[authInstance:user],view:'register')

session.clear()

return

}

}else{

log.info("ValidationError-RegisterUser")

session.clear()

render(model:[authInstance:user],view:'register')

}

}

}

def signIn = {

def authToken = new UsernamePasswordToken(params.username, params.password)



// Support for "remember me"

if (params.rememberMe) {

authToken.rememberMe = true

}



try{

// Perform the actual login. An AuthenticationException

// will be thrown if the username is unrecognised or the

// password is incorrect.

this.jsecSecurityManager.login(authToken)



// If a controller redirected to this page, redirect back

// to it. Otherwise redirect to the root URI.

def targetUri = params.targetUri ?: "/"



log.info "Redirecting to '${targetUri}'."

redirect(uri: targetUri)

}

catch (AuthenticationException ex){

// Authentication failed, so display the appropriate message

// on the login page.

log.info "Authentication failure for user '${params.username}'."

flash.message = message(code: "login.failed")

// Keep the username and "remember me" setting so that the

// user doesn't have to enter them again.

def m = [ username: params.username ]

if (params.rememberMe) {

m['rememberMe'] = true

}



// Remember the target URI too.

if (params.targetUri) {

m['targetUri'] = params.targetUri

}



// Now redirect back to the login page.

redirect(action: 'login', params: m)

}

}



def signOut = {

// Log the user out of the application.

SecurityUtils.subject?.logout()



// For now, redirect back to the home page.

redirect(uri: '/')

}



def unauthorized = {

render 'You do not have permission to access this page.'

}

}


Listing 5: authController.groovy

The next installation of the Grails Portal series will show how to build Grails Service classes with GORM and implement the portal user interface. It will also start to explain how to use the Grails WebFlow feature to orchestrate web-based wizards.
Learn More

The files for this article are contained on my website at http://www.webtech20.com/articlel.zip.

JSecurity plugin - http://grails.org/JSecurity+Plugin

Grails Framework Reference Guide - http://grails.org/doc/1.1.x/

Java Serialization - http://java.sun.com/developer/technicalArticles/Programming/serialization/

JSecurity web site - http://cwiki.apache.org/confluence/display/SHIRO/IndexThe Definitive Guide to Grails (second edition) by Graeme Rocher and Jeff Brown ISBN:9781590599952

Enterprise Patterns and MDA: Building Better Software with Achetype Patterns and UML by Jim Arlow, Ila Neustadt Addison Wesley Press ISBN-13: 978-0321112309

1 comments:

Jay said...

Good so far. When is part 2 & 3 going to be ready?