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).
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.
- This file is included in the sample code. You can rename it conference.roo.
- Create a new directory called conference and copy conference.roo in it.
- Open your operating system command-line shell.
- Go to the conference directory you just created.
- Fire the
script --file conference.roocommand.
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?
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
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.
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.
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.
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.
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.
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 (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:
- Create a directory called feedback.
- From your operating system command line, go to the feedback directory.
- Open the Roo shell by typing the
roocommand. - Type
project --topLevelPackage com.dw.roo.feedbackto create a new Maven project. - 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.
- You can introspect the database schema using
database introspect --schema feedback_schema. Thedatabase introspectcommand 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. - 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 engineercommand 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. - The next step is to generate the controllers for your application.
You can do that by firing
controller all --package ~.web. - 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 domvn clean install tomcat:run. To make it work, we have to change the hiberate.ejb.naming_strategy toDefaultNamingStrategy, as shown below:<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultNamingStrategy"/>
- 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).
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Part 2 source code | os-springroo2-sample_code.zip | 11KB | HTTP |
Information about download methods
Learn
- Be sure to read the rest of this Spring Roo series:
- Part 1: Building from source
- Part 3: Developing Spring Roo add-ons
- Part 4: Rapid application development in cloud with Spring Roo and Cloud Foundry
- Part 5: Write advanced and wrapper Spring Roo add-ons
- Part 6: Develop Spring MVC and GWT apps using Spring Roo 1.2 and deploy them on Cloud Foundry
- Part 7: Develop Spring MongoDB Applications using Spring Roo
-
Learn more about Spring Roo.
-
Check out Spring
Roo Source Code.
-
Peruse the developerWorks Java
technology zone for hundreds of articles about every aspect of Java
programming.
-
To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Follow developerWorks on Twitter.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products, as well as our most popular articles and tutorials.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
Get products and technologies
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
- Download
IBM product evaluation versions
or explore
the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from
DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
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.
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.




