Introducing Spring Roo, Part 2: Developing an application with Spring Roo

Creating a full-fledged enterprise application using Spring Roo

In Part 1 of this series, we looked at building a CRUD-based application in minutes with Roo. Here in Part 2, we will extend that application to a full-fledged enterprise application by adding features such as Spring security, email support, and many others.

Shekhar Gulati, Senior Consultant, Xebia

Shekhar Gulati is a Java consultant working with Xebia India. He has six years of enterprise Java experience. He has extensive experience in Spring portfolio projects, such as Spring, Spring-WS, and Spring Roo. His interests are Spring, NoSQL databases, Hadoop, RAD frameworks like Spring Roo, cloud computing (mainly PaaS services like Google App Engine, CloudFoundry, OpenShift), Hadoop. He is an active writer and writes for JavaLobby, Developer.com, IBM developerWorks and his own blog at http://whyjava.wordpress.com/. You can follow him on twitter @ http://twitter.com/#!/shekhargulati.



10 April 2012 (First published 01 February 2011)

Also available in Russian Japanese Vietnamese Portuguese

In Part 1 of this series on Spring Roo, we built a small enterprise conference application using Spring Roo. Here, we will extend that simple CRUD web application into a full-fledged enterprise application using Spring Roo. Before we get started, make sure you have installed Spring Roo and have downloaded SpringSource Tool Suite (see Part 1 for more information).

Let's get started

To extend our conference application, we need to recreate the application from Part 1. You can either follow the previous instructions or use Roo's script command. script executes all commands specified in a resource file. If you followed Part 1, you noticed that Roo created a file named log.roo, which contains all the commands fired on the Roo shell. We will execute that log.roo file and recreate the application.

  1. This file is included in the sample code. You can rename it conference.roo.
  2. Create a new directory called conference and copy conference.roo in it.
  3. Open your operating system command-line shell.
  4. Go to the conference directory you just created.
  5. Fire the script --file conference.roo command.

script will recreate the application in a few seconds if you have the required JARs in your Maven repository. Otherwise, it will take more time since it has to download all the JARs. The script command is useful in that you can utilize it as a template to create Spring-managed projects.

Before we move forward, import the Maven project in STS. STS comes pre-bundled with the Maven Eclipse plug-in. You should import the project by selecting File > Import > Maven > Existing Maven Projects, then selecting the project's directory. We are importing the project in STS since we will be writing some custom code later.

The web application we have created so far works, and we can test it manually by creating, reading, updating, and deleting the Speaker and Talk entities. But wouldn't it be great if we could automate this process?


Automated web testing

Here comes the next feature of Spring Roo: Selenium testing support. Selenium is a robust set of tools that support rapid development of test automation for web-based applications. To add Selenium testing support to your application, execute the commands shown below:

	selenium test --controller ~.web.SpeakerController
	selenium test --controller ~.web.TalkController

The selenium test command will create Selenium test for the Speaker and Talk controllers. This command has one required attribute called controller to specify the name of the controller to create the Selenium test. This command also has two optional attributes called name and serverUrl to specify the name of the Selenium test and the server where the web application is available. Spring Roo will also add the Selenium Maven plug-in when you fire the selenium test command.

Above, we created Selenium test cases for our controller, but before we run them, we need to fix a small defect in the Selenium test suite created by Spring Roo. We added a constraint in our Speaker entity that age should be between 25 and 60, but the test suite does not take that constraint into consideration. It used a value of age as 1, so the test will fail. We need to modify a file named test-speaker.xhtml and update the section as shown in Listing 1.

Listing 1. Modification of test-speaker.xhtml
		<tr>
			<td>type</td>
			<td>_age_id</td>
			<td>1</td>
		</tr>

to

		<tr>
			<td>type</td>
			<td>_age_id</td>
			<td>26</td>
		</tr>

This defect will be fixed in a future release of Spring Roo.

To run the Selenium test cases, we need to start the Tomcat server. You can start the it using the Maven command mvn tomcat:run. By default, all web applications created using Roo have Maven plug-ins for Tomcat and Jetty web servers. To run selenium test, execute Maven command mvn selenium:selenese.

This will launch a Firefox browser to run the Selenium test cases. During execution of the tests, you should see an image similar to Figure 1.

Figure 1. Selenium tests
Screenshot of the tool executing the Selenium Tests, with the delcarations in the top left, a control panel for running tests in the top right, and the running application in the bottom.

At present, anyone can access our application and perform create, update, and delete on Speaker and Talk. In a real-time application, there is security on who can do which operation.


Secure the web application

Roo uses Spring Security to add security to your application in one line. Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

Adding Spring Security

To add Spring Security, type the following command: security setup.

This command will add all the required Spring Security JARs and will set up the basic security for your application. This command also creates other files, but the one that is important is applicationContext-security.xml, which contains all the bean definitions related to security. The applicationContext-security.xml context looks like the content in Listing 2. I have replaced the password hashed with dots to make it more readable.

Listing 2. Context of applicationContext-security.xml
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans \
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security \
http://www.springframework.org/schema/security/spring-security-3.0.xsd">

	<!-- 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="/choices/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/member/**" access="isAuthenticated()" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
    </http>

	<!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
    	<!-- SHA-256 values can be produced using \
'echo -n your_desired_password | sha256sum' \
(using normal *nix environments) -->
    	<authentication-provider>
	    	<password-encoder hash="sha-256"/>
	        <user-service>
	            <user name="admin" password="..." authorities="ROLE_ADMIN"/>
		        <user name="user" password="..." authorities="ROLE_USER"/>
		    </user-service>
    	</authentication-provider>
	</authentication-manager>

</beans:beans>

The security configured by Roo is generic and has no reference to our application. Keep in mind that Roo helps in setting up or configuring an application for a quick start, but it is the developer's responsibility to customize the end product. In this case, Roo has merely provided a template for Spring Security, and it is our responsibility to customize it to our needs.

Customizing Spring Security

In our application, anybody can create a Speaker, but only a Speaker can create a Talk. We need to modify applicationContext-security.xml as shown below. Listing 3 only shows the part of XML, which needs modification.

Listing 3. Modifying applicationContext-security.xml
<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="/talks/**" access="hasRole('ROLE_USER')"/>
        <intercept-url pattern="/speakers/**" access="permitAll" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
</http>

I have updated the intercept-url so that only users who have the role user can create a Talk, and all users are permitted to register themselves as a Speaker.

The Roo-generated Spring Security shown above uses an in-memory authentication provider configured under the <user-service> tag. Since our application manages Speaker entities, we should build a custom authentication provider that uses Speaker data. For authentication, we will use Speaker email as the username and add a password field to the Speaker entity, which we will use as an authentication password.

Once again, I use the Roo shell to add the password field to the Speaker entity:

field string --class ~.domain.Speaker --fieldName password --notNull --sizeMin 6 –sizeMax
10

I have also added the constraint that the password should be not null and the password should be between 6 and 10 characters long.

Since we are using email and password as authentication parameters, we would like to find a Speaker for a given email and password. Spring Roo provides a facility to create finders for your application using the finder add command:

finder add --finderName findSpeakersByEmailAndPasswordEquals --class ~.domain.Speaker

You can find all the finders for an entity using the finder list command. finder add writes the finder code in the Speaker_Roo_Finder.aj file and writes some view-related files. This allows you to search for speakers from the GUI.

Writing custom AuthenticationProvider

We will write a custom Authentication provider by extending a class called AbstractUserDetailsAuthenticationProvider, which works with username/password like authentication. The classes that extend AbstractUserDetailsAuthenticationProvider have to provide implementation for its two abstract methods: additionalAuthenticationChecks and retrieveUser. The provider calls the retrieveUser method to authenticate the Speaker using the entered email and password. The database lookup for the Speaker is done using the finder we created above. If the Speaker is found, then GrantedAuthority ROLE_USER is assigned to the Speaker. The method finally returns a populated UserDetails object if the login succeeded or throws a BadCredentialsException with the appropriate message if not (see Listing 4).

Listing 4. Custom authentication
package com.dw.roo.conference.security;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityNotFoundException;
import javax.persistence.NonUniqueResultException;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.\
dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;

import com.dw.roo.conference.domain.Speaker;

public class ConferenceAuthenticationProvider extends \
AbstractUserDetailsAuthenticationProvider {

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, \
UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        // TODO Auto-generated method stub

    }

    @Override
    protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken \
authentication) throws AuthenticationException {
        String password = (String) authentication.getCredentials();
        if (!StringUtils.hasText(password)) {
            throw new BadCredentialsException("Please enter password");
        }
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        try {
            Speaker speaker = Speaker.findSpeakersByEmailAndPasswordEquals(username, \
password).getSingleResult();
            authorities.add(new GrantedAuthorityImpl("ROLE_USER"));
        } catch (EmptyResultDataAccessException e) {
            throw new BadCredentialsException("Invalid username or password");
        } catch (EntityNotFoundException e) {
            throw new BadCredentialsException("Invalid user");
        } catch (NonUniqueResultException e) {
            throw new BadCredentialsException("Non-unique user, contact administrator");
        }
        return new User(username, password, true, // enabled
                true, // account not expired
                true, // credentials not expired
                true, // account not locked
                authorities);
    }
}

In the applicationContext-security.xml, we have to define the conferenceAuthenticationProvider bean and replace the in-memory authentication provider generated by Roo with our conferenceAuthenticationProvider as in Listing 5.

Listing 5. conferenceAuthenticationProvider
<beans:bean name="conferenceAuthenticationProvider"
class="com.dw.roo.conference.security.ConferenceAuthenticationProvider">
</beans:bean>

<!-- Configure Authentication mechanism -->
<authentication-manager alias="authenticationManager">
	<authentication-provider ref="conferenceAuthenticationProvider"/>
</authentication-manager>

Now if you start up the server and try to create a Talk, you will be presented with a login screen where you have to enter the email and password of the Speaker you created. The intent of building the custom authentication provider was not to build an ideal authentication provider but to show you what Roo does and what you have to do yourself.


Email notification

In our application, Speakers should receive an email when they create a Talk, so let's add email support to our application. We will use Gmail as our SMTP server to focus on sending email using Roo. Adding email support to an application is done using the following command:

email sender setup --hostServer smtp.gmail.com --username \
<Your email address> --password <Your email password> --port 587 --protocol SMTP

The email sender command installs a Spring JavaMailSender in your project. You can change email-related properties in the email.properties file.

After a Talk is created, we need to send an email. For that, we need to add an email field in TalkController. To add an email field, type:

field email template --class ~.web.TalkController

This will add a MailSender template and a sendMessage method to TalkController. Now we need to trigger the sendMessage method after a Talk is persisted in the database. As all the code for the TalkController exists in TalkController_Roo_Controller.aj file, the easiest way to accomplish this task is to create an encodeUrlPathSegment method from the .aj file to the TalkController class and add a call to the sendMessage method after the talk.persist() line, as shown in Listing 6.

Listing 6. Creating an encodeUrlPathSegment method from the .aj file to TalkController
public class TalkController {

	@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);
	}

	@RequestMapping(method = RequestMethod.POST)
	public String create(@Valid Talk talk, BindingResult result, Model model,
			HttpServletRequest request) {
		if (result.hasErrors()) {
			model.addAttribute("talk", talk);
			return "talks/create";
		}
		talk.persist();
		sendMessage("spring.roo.playground@gmail.com", "Your talk is created",
				talk.getSpeaker().getEmail(), \
"Congrats your talk is created");
		return "redirect:/talks/"
				+ encodeUrlPathSegment(talk.getId().toString(), request);
	}

	private String encodeUrlPathSegment(String pathSegment,
			HttpServletRequest request) {
		String enc = request.getCharacterEncoding();
		if (enc == null) {
			enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
		}
		try {
			pathSegment = UriUtils.encodePathSegment(pathSegment, enc);
		} catch (UnsupportedEncodingException uee) {
		}
		return pathSegment;
	}
}

Hereafter, Speakers will receive an email in their specified accounts after a Talk is created.


Internationalization support

As we are building an Internet-based web application, it is important to support different languages so users from different geographies can utilize our application. Spring Roo adds internationalization support by using the web mvc install language command, which installs a new language in your application. For example, the commands for Spanish and Italian are:

web mvc install language --code es 
web mvc install language --code it

Roo currently support six languages, and you can write an add-on language for others of your choice. Now when the application is run, two flags are displayed (Italy's and Spain's), along with the British flag. If you click on any of these flags, you will view the web application in the language corresponding to that flag.


Socialize your web application

This is the age of social media, and social features are commonly added to current applications. It would make sense to add video of Talks. Roo provides support for embedding videos uploaded to YouTube, Vimeo, Viddler, and Google Video, etc. To embed a video, use the following command:

web mvc embed video --provider VIMEO --videoId 16069687

If you start the server and launch your application in a browser, you will be able to watch the video embedded above. Similarly, you could have added YouTube or Viddler videos.

Roo also provides you an option to embed Twitter messages, documents, stock tickers, maps, photos, and video streams in your application. The various commands are in Listing 7.

Listing 7. Embed commands
web mvc embed document 
web mvc embed finances 
web mvc embed map
web mvc embed photos 
web mvc embed stream video 
web mvc embed twitter
web mvc embed video

Database reverse engineering

Database reverse engineering (DBRE) allows you to introspect an existing database and expose it as an application. To show how DBRE works, I am going to create a feedback application from an existing feedback schema. I will use MySQL as a database.

Before we launch Roo, we have to create a schema in our MySQL installation. Run the SQL script in Listing 8 to create a feedback schema in your MySQL database.

Listing 8. SQL script to create feedback schema
create database feedback_schema;
use feedback_schema;
CREATE TABLE feedback (
  id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  TalkTitle VARCHAR(45) NOT NULL,
  SpeakerName VARCHAR(45) NOT NULL,
  Feedback VARCHAR(4000) NOT NULL,
  PRIMARY KEY (id)
)
ENGINE = InnoDB;

This is a simple schema with one table and without any relationships, but Roo can successfully reverse-engineer a complex schema with multiple tables and relationships.

After you have run the above SQL script and generated your schema, create a feedback application:

  1. Create a directory called feedback.
  2. From your operating system command line, go to the feedback directory.
  3. Open the Roo shell by typing the roo command.
  4. Type project --topLevelPackage com.dw.roo.feedback to create a new Maven project.
  5. In this application, we will be using MySQL as our database. To set up the persistence for your application, use:
    persistence setup --provider HIBERNATE 
    --database MYSQL --databaseName feedback_schema 
      --userName root --password password

    As I created the schema with root user, I am using root as my username. Please enter the username and password with which you created your schema. This command will also add all required JARs for persistence.

  6. You can introspect the database schema using database introspect --schema feedback_schema. The database introspect command shows the metadata related to the database schema. This command will show you the metadata for your schema on Roo shell console. You can also export the metadata xml to a file using --file attribute.
  7. After you have done introspection for your database schema, you can reverse-engineer that schema using database reverse engineer --schema feedback_schema --package ~.domain.

    The database reverse engineer command has two required attributes, schema and package, for specifying the name of the schema you want to reverse-engineer and the package in which Roo will generate the sources. This will create all the entities in com.dw.roo.feedback.domain package.

  8. The next step is to generate the controllers for your application. You can do that by firing controller all --package ~.web.
  9. Before we run our application, we need to make a small change in a property in persistence.xml. The property hibernate.ejb.naming_strategy is using ImprovedNamingStrategy, which does not work for MySQL databases, and you will get an exception if you do mvn clean install tomcat:run. To make it work, we have to change the hiberate.ejb.naming_strategy to DefaultNamingStrategy, as shown below:
    <property name="hibernate.ejb.naming_strategy" 
    value="org.hibernate.cfg.DefaultNamingStrategy"/>
  10. Now you can run the feedback application using the Maven command mvn clean install tomcat:run.

You can download the source code for conference and feedback (see Download).


Conclusion

As this point, we have extended our simple CRUD web application to a full-fledged enterprise application. We demonstrated how simple it is to add features like Selenium testing, Spring Security, internationalization support, and others. I also showed how to create an application from an existing database using the Spring Roo database reverse-engineering feature. There are still lots of features like JMS, Solr, and JSON support, etc. that Roo can easily add to your application.

In Part 3 of this series, I talk about how we can port the conference application to Google App Engine.


Download

DescriptionNameSize
Part 2 source codeos-springroo2-sample_code.zip11KB

Resources

Learn

Get products and technologies

Discuss

  • Participate in developerWorks blogs and get involved in the developerWorks community.
  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=619738
ArticleTitle=Introducing Spring Roo, Part 2: Developing an application with Spring Roo
publish-date=04102012