Monday, April 4, 2011

My First Impression of Spring Roo

In my previous post, I demonstrated how to set up a Web site with Spring Roo in 10 minutes. Actually I spent several hours to get it work, and even more to write it down and posted in my blog. For a beginner, it may take longer to understand the whole architecture and start to do real work. However, the beauty of Roo is you always have a running application to play with, and you can focus on learning one technology at a time, without being distracted by other things you are not familiar.

Here are what I like the Spring Roo:

First it fulfills my dream development environment. In my dream, I want to work with the best open source tools and best Java framework (in my opinion), such as Maven, Eclipse, Spring Framework. And I want to try the cutting edge technologies like GWT, OSGi. But I always get burnt by this ambition, it is impossible for me to learn all those and get my application work at once. Thanks to Roo Add-On Developers, I can use many new tools in an integrated running application before I even understand them.

Second, I like the round-trip rapid development process. You can use Roo to change your model, and Roo will update your Java code automatically. At the same time, you can also change your Java code, and Roo will automatically fix the generated aspect code and configurations. I also tried other tools before for generating project structure, such as AppFuse, Maven Archetype. They are good tool to start a project from scratch, but you cannot do it again with more changes. Now I can continuously use Roo to grow my application, and it really saves my time.

Third, I like the script approach. Roo will keep a log file at the project folder (log.roo) with all your commands. After several round of exercises, you can just copy the script from log.roo and modify it to have a clean model, then ask Roo to run it again from scratch. I find it is very useful when I design the domain model. Other RAD tools may use UML to do the same, but I feel UML Diagram is better for communicating among people. When get down to code level, text based script is superior for talking to computer.

Last, I love the code it generates, beautiful code. Roo uses AspectJ inter type declaration (ITD) feature to hide all those boilerplate code, so the generated Java code will be very clean. After you understand how it works, you won't want to go back to the old way.

After first try of Spring Roo, I think it will be useful in two areas:

  • Rapid Prototyping. Right now as an application designer, after discussing with business, it will take me many days to build the domain model and mock up UI. When the first approach is not right (always happen), I have to throw away old code and write those boilerplate code again and again. Roo can help me build a prototype quicker, especially for domain model part.
  • Standard Application Architecture. I didn't try Add-On development, but I assume it is doable to customize Roo so it will generate code following standard in any enterprise. Therefore in any corporation, it will be very fast to start a new project, and let developer focus the business issues instead of infrastructure issues.

Here is my wish list for Spring Roo:

  • Provide richer script command to build complete domain model. It has excellent support for Entity, little support for Value Object or Domain Service. I want Roo to generate methods for me as well.
  • Support Maven structure for multiple projects. Currently seems Roo only supports one project structure. For a medium to large application, we may have to split code into several projects. Hope Roo will support it in the future.
  • Better support to Google App Engine and GWT. I will try it when 1.1.3 is released.
  • See what Roo can do after it integrates with WaveMaker. If UI design is as quick and clean as this, I can talk to customer and build running demo on the fly. That's my biggest dream.

Overall, my first impression of Spring Roo is very good. I appreciate SpringSource for providing this wonderful tool for Java developers. They are the true leader in Java Open Source Community.

Sunday, April 3, 2011

Build Open Toast Web Site with Spring Roo in 10 Minutes

Two weeks ago, I started to learn Spring Roo by its reference document and tutorials. Now it is time to do some real exercises.

After reading Ben Alex's blog about his Wedding RSVP Web site, I want to do a similar Web site for Toastmasters.

The main goal for this exercise is to register our club members' personal data and confirm their email address. The process is very simple: admin user can login and register new member; a confirmation email will be sent out to new member's email address; new member clicks the hyperlink in the email to confirm the registration.

I opened STS 2.6.0 with Spring Roo 1.1.2, and followed Ben's tutorial. After a while I got this Roo script to set up my project:

project --topLevelPackage com.opentoast --projectName OpenToast --java 6
persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY

entity --class ~.domain.Toastmaster --testAutomatically 
field string --fieldName firstName --sizeMin 2 --sizeMax 30
field string --fieldName lastName --notNull --sizeMin 2 --sizeMax 30
field string --fieldName email --sizeMax 30 --sizeMin 6
field string --fieldName phone --sizeMax 20
field boolean --fieldName confirmed

entity --class ~.domain.EmailConfirmation
field string --fieldName code --notNull 
field string --fieldName email --notNull
field date --fieldName confirmDate --type java.util.Date
field reference --fieldName toastmaster --type ~.domain.Toastmaster --notNull 
finder add --finderName findEmailConfirmationsByCodeEquals

interface --class ~.domain.ConfirmationService
class --class ~.domain.impl.ConfirmationServiceImpl

controller scaffold --entity ~.domain.Toastmaster --class ~.web.ToastmasterController
controller class --class ~.web.ConfirmationController

selenium test --controller ~.web.ToastmasterController

logging setup --package WEB --level DEBUG

security setup

email sender setup --hostServer smtp.gmail.com --protocol SMTP --port 587 --username youraccount --password yourpassword
field email template --class ~.domain.impl.ConfirmationServiceImpl

perform eclipase

The domain model only contains two entity, Toastmaster and EmailConfirmation. (I cannot use Member or User, since those are reserved SQL keyword.)

One Web controller for maintaining Toastmaster records is genereated by controller scafford command. Spring Roo will generate all CRUD jsp pages.

Another controller for new member to confirm email address made by: controller class --class ~.web.ConfirmationController.

When admin user creates a new Toastmaster, a confirmation email will be sent out, and I want this logic to be in a service, so one service interface and one implementation class are created. But Spring Roo cannot generate real business logic code for us, so I have to add code manually to interface and implementation classes.

There are other changes in this application to get it working as we want. Because the last step in Roo script is perform eclipse, so just run this script in Roo and import the result project to STS, you have a running Web application ready to play with.

So first let's add logic to ConfirmationService:

src/main/java/com/opentoast/domain/ConfirmationService.java

package com.opentoast.domain;

public interface ConfirmationService {
    void sendConfirmationEmail(Toastmaster toastmaster);
}

src/main/java/com/opentoast/domain/impl/ConfirmationServiceImpl.java

package com.opentoast.domain.impl;

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.stereotype.Component;

import com.opentoast.domain.ConfirmationService;
import com.opentoast.domain.EmailConfirmation;
import com.opentoast.domain.Toastmaster;

@Component("confirmationService")
public class ConfirmationServiceImpl implements ConfirmationService{
    @Autowired
    private transient MailSender mailTemplate;

    public void sendMessage(String mailFrom, String subject, String mailTo, String message) {
        org.springframework.mail.SimpleMailMessage simpleMailMessage = new org.springframework.mail.SimpleMailMessage();
        simpleMailMessage.setFrom(mailFrom);
        simpleMailMessage.setSubject(subject);
        simpleMailMessage.setTo(mailTo);
        simpleMailMessage.setText(message);
        mailTemplate.send(simpleMailMessage);
    }

    @Override
    public void sendConfirmationEmail(Toastmaster toastmaster) {
        //create a new email confirmation record
        EmailConfirmation emailConfirmation = new EmailConfirmation();
        emailConfirmation.setCode(UUID.randomUUID().toString());
        emailConfirmation.setEmail(toastmaster.getEmail());
        emailConfirmation.setToastmaster(toastmaster);
        emailConfirmation.persist();
        
        //build email content
        StringBuffer sb = new StringBuffer();
        sb.append("Please confirm your email address '");
        sb.append(toastmaster.getEmail());
        sb.append("' by clicking http://localhost:8080/OpenToast/confirmation/");
        sb.append(emailConfirmation.getCode());
        sb.append("\n Thank you!");
        sb.append("\n\nOpen Toast Project");
        
        sendMessage("Open Toast Project <opentoastproject@gmail.com>", "Please Confirm Your Email to Open Toast", toastmaster.getEmail(), sb.toString()); 
    }

}

The ConfirmationServiceImpl is a component (annotated by @Component), so we don't need to change any configuration, and it will be automatically handled by Spring container. Next, inject this component to ToastmasterController so when admin creates a new member, it will use this service to send out email. Add create method to ToastmasterController class, and Spring Roo will automatically remove same method in aspectj file ToastmasterController_Roo_Controller.aj.

src/main/java/com/opentoast/web/ToastmasterController.java

package com.opentoast.web;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.roo.addon.web.mvc.controller.RooWebScaffold;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.opentoast.domain.ConfirmationService;
import com.opentoast.domain.Toastmaster;

@RooWebScaffold(path = "toastmasters", formBackingObject = Toastmaster.class)
@RequestMapping("/toastmasters")
@Controller
public class ToastmasterController {

    @Autowired
    private transient ConfirmationService confirmationService;

    @RequestMapping(method = RequestMethod.POST)
    public String create(@Valid Toastmaster toastmaster, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
        if (bindingResult.hasErrors()) {
            uiModel.addAttribute("toastmaster", toastmaster);
            return "toastmasters/create";
        }
        uiModel.asMap().clear();
        toastmaster.persist();
        confirmationService.sendConfirmationEmail(toastmaster);
        
        return "redirect:/toastmasters/" + encodeUrlPathSegment(toastmaster.getId().toString(), httpServletRequest);
    }

}

Here we only add code to inject ConfirmationService object, and call its sendConfirmationEmail methos when a new Toastmaster record is created.

The email contains a URL, and when new member clicks this URL, it will send HTTP GET request with the confirmation code in it. Spring MVC can handle it very nicely with URL Template /confirmation/{code}, so the code value is bound to parameter String code. By using Roo auto generated finder method EmailConfirmation.findEmailConfirmationsByCodeEquals, we can get the EmailConfirmation object used for this confirmation, and set the confirmation date, set Toastmaster object as confirmed.

So in ConfirmationController class, remove all generated methods, and add confirmByCode method to do the logic:

src/main/java/com/opentoast/web/ConfirmationController.java

package com.opentoast.web;

import java.util.Date;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.opentoast.domain.EmailConfirmation;

@RequestMapping("/confirmation/**")
@Controller
public class ConfirmationController {
    @RequestMapping(value="/confirmation/{code}", method=RequestMethod.GET)
    public String confirmByCode(@PathVariable String code, ModelMap modelMap) {
        try {
            EmailConfirmation emailConfirmation = (EmailConfirmation)EmailConfirmation.findEmailConfirmationsByCodeEquals(code).getSingleResult();
            emailConfirmation.setConfirmDate(new Date());
            emailConfirmation.getToastmaster().setConfirmed(true);
            emailConfirmation.persist();
            
            modelMap.put("confirmation", emailConfirmation);
        } catch (Exception e) {
            // ignore
        }
        return "confirmation/thanks"; 
    }

}

After member clicks URL, we want to display a thank you message, so let's change the generated index.jspx name to thanks.jspx in confirmation folder, and add a spring message:

src/main/webapp/WEB-INF/views/confirmation/thanks.jspx

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<div xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:spring="http://www.springframework.org/tags" xmlns:util="urn:jsptagdir:/WEB-INF/tags/util" version="2.0">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <jsp:output omit-xml-declaration="yes"/>
  <spring:message code="label_confirmation_index" htmlEscape="false" var="title"/>
  <util:panel id="title" title="${title}">
    <spring:message code="application_name" htmlEscape="false" var="app_name"/>
    <h3>
      <spring:message arguments="${app_name}" code="welcome_titlepane"/>
    </h3>
    <p>
      <spring:message code="thanks_text" arguments="${confirmation.email}"/>
    </p>
  </util:panel>
</div>

In order to display the message, we add the text to messages.properties:

src/main/webapp/WEB-INF/i18n/messages.properties

thanks_text=You have successfully confirmed your email {0}. Thank you for signing up Open Toast.

And update view.xml to reflect the name changes:

src/main/webapp/WEB-INF/views/confirmation/views.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
    <definition extends="public" name="confirmation/thanks">
        <put-attribute name="body" value="/WEB-INF/views/confirmation/thanks.jspx"/>
    </definition>
</tiles-definitions>
pr/> last change is to security setting. The whole Web site should only be accessed by authenticated user. We only allow admin user to maintain Toastmaster data in toastmaster path, but un-authenticated user can access confirmation path to confirm.

src/main/resources/META-INF/spring/applicationContext-security.xml

...
<!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
        <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/toastmasters/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/confirmation/**" access="permitAll" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/login**" access="permitAll" />
<intercept-url pattern="/**" access="isAuthenticated()" />
    </http>
...

If you didn't change gmail account ID and password, you can open email.properties file and update with your personal gmail account and password.

So that's it. You can test this Web application by running mvn tomcat:run in the directory you run the Roo script, and open http://localhost:8080/OpenToast. First login with admin account and add a new Toastmaster with a valid email address. Then check the email, and click the URL, you will see the thank you page. Go back to the Web site and list all Toastmasters, you will see the confirmed flag is true now.