Friday, September 14, 2012

Part 3 - Introducing Spring Security, Thymeleaf and Spring MVC

Security is one of the key aspect of an application, especially web application. In our application we intend to integrate Spring Security as we are already on Spring framework. Alternative was Apache Shiro. But we prefer Spring Security. Here is how we can integrate Spring Security with Spring MVC and Thymeleaf.
The first step to this integration is to include the Spring Security and Thymeleaf Spring Security jars into our application. This can be done by adding Maven dependencies. Here is the snippet from the parent/pom.xml file.


Listing 1 - parent/pom.xml - snippet
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- SPRING SECURITY          -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
    <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity3</artifactId>
            <version>1.0.0-beta1</version>
        </dependency>
Now the next step is to include the Spring Security filter in the web.xml. Note some subtle changes in the Spring config file changes. Spring contexts have parent child relationship. The context loaded by ContextLoaderListener is the parent and that by dispatcher servlet is child. The child context now has the beans related to view and contoller. The service and repository beans will be loaded by the parent context. The contoller accesses the service beans. Since child context has full access to parent context the controllers can easily use the service beans and not the other way round. The modified web.xml is shown in Listing 2.
Listing 2 - web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <!-- Log4j -->
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>file:${catalina.home}/temp/effectivcrm/conf/log4j.xml</param-value>
    </context-param>

    <context-param>
        <param-name>log4jExposeWebAppRoot</param-name>
        <param-value>false</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:conf/spring-*-config.xml
        </param-value>
    </context-param>

    <!-- Log4j listerner -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <!-- Spring Listeners -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>Spring MVC 3 Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath*:conf/spring-view.xml
                classpath*:conf/spring-controller.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Spring MVC 3 Servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
The spring-web-config.xml is renamed as spring-view.xml and we add the Thymeleaf Spring Security dialect here. This exposes Spring security variables to Thymeleaf. Refer to the Spring Security 3 Thymeleaf integration project for details.
Listing 3 - spring-view.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <mvc:annotation-driven />

    <mvc:resources location="classpath:/META-INF/assets/img/" mapping="/assets/img/**" />
    <mvc:resources location="classpath:/META-INF/assets/css/" mapping="/assets/css/**" />
    <mvc:resources location="classpath:/META-INF/assets/js/" mapping="/assets/js/**" />

    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">

    </bean>

    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="cacheSeconds" value="0" /> <!-- NO CACHE -->
    </bean>

    <bean id="contentNegotiatingResolver"
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="html" value="text/html" />
                <entry key="pdf" value="application/pdf" />
                <entry key="xsl" value="application/vnd.ms-excel" />
                <entry key="xml" value="application/xml" />
                <entry key="json" value="application/json" />
            </map>
        </property>
    </bean>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!-- Tiles                                -->
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->

    <bean id="tilesConfigurer" class="org.thymeleaf.extras.tiles2.spring.web.configurer.ThymeleafTilesConfigurer">
      <property name="definitions">
        <list>
          <value>classpath:/META-INF/tiles/tiles-*.xml</value>
        </list>
      </property>
    </bean>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!-- Themeleaf View Config -->
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->

    <bean id="templateResolver"
        class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">

        <property name="suffix" value=".html" />
        <property name="templateMode" value="HTML5" />
        <property name="cacheable" value="false" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />

        <property name="additionalDialects">
            <set>
                <bean class="com.effectivcrm.view.form.ExtraSpringDialect" />
                <bean class="org.thymeleaf.extras.tiles2.dialect.TilesDialect"/>
                <bean class="org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect"/>
            </set>
        </property>
    </bean>

    <bean id="tilesViewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
      <property name="viewClass" value="org.thymeleaf.extras.tiles2.spring.web.view.ThymeleafTilesView"/>
      <property name="templateEngine" ref="templateEngine" />
      <property name="characterEncoding" value="UTF-8" />
    </bean>

</beans>
 Now we introduce the Spring security configuration as shown in Listing 4.
Listing 4 - spring-security-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <security:http use-expressions="true">
        <security:form-login login-page="/signin"
            default-target-url="/listlead"
            authentication-failure-url="/signinfailure" />
        <security:intercept-url pattern="/signin*" access="permitAll" />
        <security:intercept-url pattern="/**/*.js" access="permitAll" />
        <security:intercept-url pattern="/**/*.css" access="permitAll" />
        <security:intercept-url pattern="/**/*.gif" access="permitAll" />

        <security:intercept-url pattern="/list*" access="fullyAuthenticated" />
        <security:intercept-url pattern="/create*" access="fullyAuthenticated" />

    </security:http>

    <security:authentication-manager>
      <security:authentication-provider>
        <security:user-service>
        <security:user name="dhrubo" password="123456" authorities="ROLE_USER" />
        </security:user-service>
      </security:authentication-provider>
    </security:authentication-manager>

</beans>

I will also modify the signinform.html to allow it to submit to Spring Security filter to initiate the authentication and subsequent redirection on successful authentication.
Listing 5 - signinform.html
<h3>Sign In</h3>
<form xmlns:th="http://www.thymeleaf.org"
    action="authentication" th:action="@{/j_spring_security_check}"
    method="post" class="well">
    <fieldset>
        <label>Username :</label> <input id="j_username" name="j_username"
            type="text" required="required" autofocus="autofocus"
            class="input span5" placeholder="Username" /> <label>Password
            :</label> <input id="j_password" name="j_password" type="password"
            required="required" class="input span5"
            placeholder="Password" />
    </fieldset>
    <button type="submit" class="btn btn-success">Sign In</button>
    <button type="submit" class="btn btn-info">Recover password</button>
</form>

The header.html is also modified to check if authentication is working fine and also check Thymeleaf Spring Security 3 integration by printing the name of the logged in user.
Listing 6 - header.html
<div xmlns:th="http://www.thymeleaf.org"
    class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">

        <div class="container-fluid">
            <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> <a class="brand" href="#">Project name</a>
            <div class="nav-collapse collapse">

                <p class="navbar-text pull-right">
                    Logged in as <a href="#" class="navbar-link" th:text="${#authentication.name}">Username</a>
                </p>
                <ul class="nav">
                    <li class="active"><a href="#">Home</a></li>
                    <li><a href="#about">About</a></li>
                    <li><a href="#contact">Contact</a></li>

                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
</div>
We will now add 1 controller mapping - signin in the GuestController.
Listing 7 - GuestController.java
/**
 *
 */
package com.effectivcrm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author Dhrubo
 *
 */
@Controller
public class GuestController {

    @RequestMapping(value="/signin")
    public String gotoSignIn(){
        return "signin";
    }

    @RequestMapping(value="/")
    public String index(){
        return "signin";
    }

}

Wednesday, September 12, 2012

Part 3 - Integrating Tiles, Thymeleaf and Spring MVC 3

In this post I will demonstrate how to integrate Apache Tiles with Thymeleaf. This is very simple. The first step is to include the tiles and thymeleaf-tiles extension dependencies. I will include them in the pom.xml. Note we wil lbe using Tiles 2.2.2

Listing 1 - parent/pom.xml --- thymeleaf-tiles and tiles dependencies

        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!-- Tiles -->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->

<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>${tiles.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-template</artifactId>
<version>${tiles.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>${tiles.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>${tiles.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-tiles2</artifactId>
<version>1.0.0-beta2</version>
</dependency>

<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>

 

Next step is to include the ThymeleafTilesView and TilesDialect in the spring configuration as shown in Listing 2.

Listing 2 - view/spring-web-config.xml

<bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
<property name="templateResolver" ref="templateResolver" />

<property name="additionalDialects">
<set>
<bean class="com.effectivcrm.view.form.ExtraSpringDialect" />
<bean class="org.thymeleaf.extras.tiles2.dialect.TilesDialect"/>
</set>
</property>
</bean>

<bean id="tilesViewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
<property name="viewClass" value="org.thymeleaf.extras.tiles2.spring.web.view.ThymeleafTilesView"/>
<property name="templateEngine" ref="templateEngine" />
<property name="characterEncoding" value="UTF-8" />
</bean>

 

Now we will include the tiles-definition file as shown in Listing 3.

Listing 3 - /view/src/main/resources/META-INF/tiles/tiles-defs.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>
<!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 name="createlead" template="layout">
<put-attribute name="title" value="Tiles tutorial homepage" />
<put-attribute name="header" value="header" />
<put-attribute name="navigation" value="navigation" />
<put-attribute name="control" value="createleadform" />
<put-attribute name="footer" value="Foot" />
</definition>
</tiles-definitions>

 We are not done yet. We will now create the layout as shown in listing 4.

Listing 4 - layout.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:tiles="http://www.thymeleaf.org">

<head>

<title>effectiv:Home</title>

<link rel="stylesheet" th:href="@{/assets/css/bootstrap.min.css}" />
<link rel="stylesheet"
th:href="@{/assets/css/bootstrap-responsive.min.css}" />

<link rel="stylesheet" th:href="@{/assets/css/thymeleaf-demo.css}" />

<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}

.sidebar-nav {
padding: 9px 0;
}
</style>

<!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->

<!-- Le fav and touch icons -->
<link rel="shortcut icon" href="../assets/ico/favicon.ico" />

<link rel="apple-touch-icon-precomposed" sizes="144x144"
href="../assets/ico/apple-touch-icon-144-precomposed.png" />
<link rel="apple-touch-icon-precomposed" sizes="114x114"
href="../assets/ico/apple-touch-icon-114-precomposed.png" />
<link rel="apple-touch-icon-precomposed" sizes="72x72"
href="../assets/ico/apple-touch-icon-72-precomposed.png" />
<link rel="apple-touch-icon-precomposed"
href="../assets/ico/apple-touch-icon-57-precomposed.png" />

</head>

<body>

<div tiles:include="header">
Header
</div>

<div class="container-fluid">
<div class="row-fluid">
<div class="span3">
<div tiles:include="navigation">Navigation</div>

<!--/.well -->
</div>
<!--/span-->

<div class="span9">
<div tiles:include="control">Control</div>

</div>
<!--/span-->

</div>
<!--/row-->

<hr></hr>

<footer>
<p>&copy; Company 2012</p>
</footer>

</div>
<!--/.fluid-container-->

</body>
</html>

Note the tiles:include attribute and the xml namespace declaration. This is where the filler pages will be included by Tiles framework.

Listing 5 - header.html

<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">

<div class="container-fluid">
<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> <a class="brand" href="#">Project name</a>
<div class="nav-collapse collapse">

<p class="navbar-text pull-right">
Logged in as <a href="#" class="navbar-link">Username</a>
</p>
<ul class="nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>

</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>

Listing 6 - navigation.html

<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Sidebar</li>
<li class="active"><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li><a href="#">Link</a></li>
<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
</ul>

</div>

Listing 7 - createleadform.html

<div class="well"
xmlns:th="http://www.thymeleaf.org">

<form class="form-horizontal" action="#" th:action="@{/savelead}"
name="saveleadform" id="saveleadform" th:object="${lead}"
method="post">

<h4>New Lead</h4>

<div class="alert alert-success" th:if="${lead.id !=null}">
<strong>Well done!</strong> You successfully read this important
alert message.
</div>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>

<div class="row-fluid">

<div class="span4">
<div class="control-group"
th:class="${#fields.hasErrors('firstName')}? 'control-group error'">
<label class="control-label" for="firstName">First Name</label>
<div class="controls">
<input type="text" th:field="*{firstName}"
placeholder="First Name"></input> <span class="help-inline error"
th:if="${#fields.hasErrors('firstName')}"
th:errors="*{firstName}">First Name is required.</span>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="lastName">Last Name</label>
<div class="controls">
<input type="text" placeholder="Last Name" th:field="*{lastName}"></input>
<span class="help-inline" th:if="${#fields.hasErrors('lastName')}"
th:errors="*{lastName}">Last Name is required.</span>

</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="opportunityAmount">Opportunity
Amount</label>
<div class="controls">
<input type="text" placeholder="Opportunity Amount"
th:field="*{opportunityAmount}"></input> <span
class="help-inline"
th:if="${#fields.hasErrors('opportunityAmount')}"
th:errors="*{opportunityAmount}">Must be a valid amount.</span>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="converted">Converted</label>
<div class="controls">
<input type="checkbox" disabled="true" th:field="*{converted}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="email">Email</label>
<div class="controls">
<div class="input-prepend">
<input type="text" placeholder="Email" th:field="*{email}"></input>
<span class="add-on"><i class="icon-envelope"></i></span>
</div>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="mobile">Mobile</label>
<div class="controls">
<input type="text" placeholder="Mobile" th:field="*{mobile}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="workPhone">Work Phone</label>
<div class="controls">
<input type="text" placeholder="Work Phone"
th:field="*{workPhone}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="fax">Fax</label>
<div class="controls">
<input type="text" placeholder="Fax" th:field="*{fax}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span8">
<div class="control-group">
<label class="control-label" for="description">Description</label>
<div class="controls">

<textarea rows="4" cols="20" placeholder="Description"
th:field="*{description}"></textarea>
</div>
</div>
</div>
</div>

<hr></hr>
<h5>Address</h5>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="city">City</label>
<div class="controls">

<input type="text" id="city" placeholder="City"
th:field="*{address.city}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="country">Country</label>
<div class="controls">
<input type="text" id="country" placeholder="country"
th:field="*{address.country}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="postcode">Post Code</label>
<div class="controls">

<input type="text" id="postcode" placeholder="Post Code"
th:field="*{address.postcode}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="state">State</label>
<div class="controls">
<input type="text" id="state" placeholder="State"
th:field="*{address.state}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span8">
<div class="control-group">

<label class="control-label" for="street">Street</label>
<div class="controls">

<textarea rows="4" cols="60" placeholder="Street"
th:field="*{address.street}"></textarea>
</div>
</div>

</div>
</div>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>
</form>
</div>

Note that we have worked around the original createlead.html file and splitted it into some fragments and this is rendered now via the tiles framework implementing the composite view pattern.Now you can type the same URL - http://localhost:8080/ecrm/createlead and get the create lead view. You can read the Apache Tiles documentation to know more about tiles and its benefits.

In the next episode I will integrate Spring Security 3 with Thymeleaf and Spring MVC. As always the code is available on SVN and you can also find a new list lead controller and view. As an exercise you can try and convert that into tiles view.

Tuesday, September 11, 2012

Part 3 - Validation

In this episode, I will show in easy steps how to integrate Hibernate Validator - the reference implementation of JSR 303 - Bean Validation into our application. Hibernate Validator is already part of our application thanks to the inclusion of the relevant jar in the parent pom file. Let us add a simple validation into our domain object - Lead - let us not allow a blank for the first name. The modified Lead class is shown in Listing 1.

Listing 1 - Lead.java

package com.effectivcrm.domain;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;

import org.hibernate.envers.Audited;
import org.hibernate.validator.constraints.NotEmpty;

/**
* The persistent class for the leads database table.
*
*/
@Entity
@Table(name = "t_lead")
@Audited
public class Lead extends PersistentObject {
private static final long serialVersionUID = 1L;

@Getter @Setter
@NotEmpty
@Column(name = "first_name", length = 100)
private String firstName;

@Getter @Setter
@Column(name = "last_name", length = 100)
private String lastName;

@Getter @Setter
@Column(name = "opportunity_amount")
private double opportunityAmount;

@Getter @Setter
@Column(name = "converted")
private boolean converted;

@Getter @Setter
@Column(name = "description", length = 250)
private String description;

@Getter @Setter
@Column(name = "email", length = 100)
private String email;

@Getter @Setter
@Column(name = "mobile", length = 30)
private String mobile;

@Getter @Setter
@Column(name = "work_phone", length = 30)
private String workPhone;

@Getter @Setter
@Column(name="fax")
private String fax;

@Getter @Setter
@Embedded
private Address address;

}

 

Next we will have to modify our view to to handle the error. Note the modified listing around the first name field.

Listing 2 - createlead.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">

<head th:include="common :: headerFragment">

<title>effectiv:Home</title>

</head>

<body>

<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">

<div class="container-fluid">
<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> <a class="brand" href="#">Project name</a>
<div class="nav-collapse collapse">

<p class="navbar-text pull-right">
Logged in as <a href="#" class="navbar-link">Username</a>
</p>
<ul class="nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>

</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>

<div class="container-fluid">
<div class="row-fluid">
<div class="span3">

<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Sidebar</li>
<li class="active"><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li><a href="#">Link</a></li>
<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
</ul>

</div>
<!--/.well -->
</div>
<!--/span-->

<div class="span9">

<div class="well">

<form class="form-horizontal" action="#" th:action="@{/savelead}"
th:object="${lead}" method="post">

<h4>New Lead</h4>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>

<div class="row-fluid">

<div class="span4">
<div class="control-group" th:class="${#fields.hasErrors('firstName')}? 'control-group error'">
<label class="control-label" for="firstName">First Name</label>
<div class="controls">
<input type="text" th:field="*{firstName}"
placeholder="First Name"></input> <span
class="help-inline error"
th:if="${#fields.hasErrors('firstName')}"
th:errors="*{firstName}">First Name is required.</span>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="lastName">Last Name</label>
<div class="controls">
<input type="text" placeholder="Last Name"
th:field="*{lastName}"></input> <span class="help-inline"
th:if="${#fields.hasErrors('lastName')}"
th:errors="*{lastName}">Last Name is required.</span>

</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="opportunityAmount">Opportunity
Amount</label>
<div class="controls">
<input type="text" placeholder="Opportunity Amount"
th:field="*{opportunityAmount}"></input> <span
class="help-inline"
th:if="${#fields.hasErrors('opportunityAmount')}"
th:errors="*{opportunityAmount}">Must be a valid
amount.</span>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="converted">Converted</label>
<div class="controls">
<input type="checkbox" disabled="true" th:field="*{converted}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="email">Email</label>
<div class="controls">
<div class="input-prepend">
<span class="add-on"><i class="icon-envelope"></i></span> <input
type="text" placeholder="Email" th:field="*{email}"></input>
</div>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="mobile">Mobile</label>
<div class="controls">
<input type="text" placeholder="Mobile" th:field="*{mobile}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="workPhone">Work Phone</label>
<div class="controls">
<input type="text" placeholder="Work Phone"
th:field="*{workPhone}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="fax">Fax</label>
<div class="controls">
<input type="text" placeholder="Fax" th:field="*{fax}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span8">
<div class="control-group">
<label class="control-label" for="description">Description</label>
<div class="controls">

<textarea rows="4" cols="20" placeholder="Description"
th:field="*{description}"></textarea>
</div>
</div>
</div>
</div>

<hr></hr>
<h5>Address</h5>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="city">City</label>
<div class="controls">

<input type="text" id="city" placeholder="City"
th:field="*{address.city}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="country">Country</label>
<div class="controls">
<input type="text" id="country" placeholder="country"
th:field="*{address.country}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span4">
<div class="control-group">
<label class="control-label" for="postcode">Post Code</label>
<div class="controls">

<input type="text" id="postcode" placeholder="Post Code"
th:field="*{address.postcode}"></input>
</div>
</div>
</div>

<div class="span4">
<div class="control-group">
<label class="control-label" for="state">State</label>
<div class="controls">
<input type="text" id="state" placeholder="State"
th:field="*{address.state}"></input>
</div>
</div>
</div>
</div>

<div class="row-fluid">
<div class="span8">
<div class="control-group">

<label class="control-label" for="street">Street</label>
<div class="controls">

<textarea rows="4" cols="60" placeholder="Street"
th:field="*{address.street}"></textarea>
</div>
</div>

</div>
</div>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>
</form>
</div>

</div>
<!--/span-->

</div>
<!--/row-->

<hr></hr>

<footer>
<p>&copy; Company 2012</p>
</footer>

</div>
<!--/.fluid-container-->

<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../assets/js/jquery.js"></script>
<script src="../assets/js/bootstrap-transition.js"></script>
<script src="../assets/js/bootstrap-alert.js"></script>
<script src="../assets/js/bootstrap-modal.js"></script>

<script src="../assets/js/bootstrap-dropdown.js"></script>
<script src="../assets/js/bootstrap-scrollspy.js"></script>
<script src="../assets/js/bootstrap-tab.js"></script>
<script src="../assets/js/bootstrap-tooltip.js"></script>
<script src="../assets/js/bootstrap-popover.js"></script>
<script src="../assets/js/bootstrap-button.js"></script>

<script src="../assets/js/bootstrap-collapse.js"></script>
<script src="../assets/js/bootstrap-carousel.js"></script>
<script src="../assets/js/bootstrap-typeahead.js"></script>

</body>
</html>

 

Last but not the least if there is a problem with binding the domain object from request we need to redirect it back to the original form page - createlead. This is handled int he controller as shown in Listing 3.

Listing 3 - SaveLeadController.java

package com.effectivcrm.controller;

import javax.validation.Valid;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import com.effectivcrm.domain.Lead;

@Controller
@Slf4j
public class SaveLeadController {

@RequestMapping("/savelead")
public String saveLead(@Valid Lead lead, BindingResult result){
log.info("Lead for saving = {}", lead);
if (result.hasErrors()) {
return "createlead";
} else {
return "Done";
}
}

}

In the next post, I will create the page for listing of leads and then move on to integrate Tiles, Spring Security and yes i18n. I intend to keep this application simple and I am fine with full page loads and keeping it away from Ajax at the moment.

Monday, September 10, 2012

Part 3 - Introducing the domain objects and first lead create view

As I decided that I will go with static forms instead of dynamic ones, I set sail creating the createlead.html page with Thymeleaf support and Spring MVC.The listing below shows the form:

Listing 1 - createlead.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">

<head th:include="common :: headerFragment">

<title>effectiv:Home</title>

</head>

<body>

<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">

<div class="container-fluid">
<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> <a class="brand" href="#">Project name</a>
<div class="nav-collapse collapse">

<p class="navbar-text pull-right">
Logged in as <a href="#" class="navbar-link">Username</a>
</p>
<ul class="nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>

</ul>
</div>
<!--/.nav-collapse -->
</div>
</div>
</div>

<div class="container-fluid">
<div class="row-fluid">
<div class="span3">

<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Sidebar</li>
<li class="active"><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>

<li><a href="#">Link</a></li>
<li class="nav-header">Sidebar</li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
<li><a href="#">Link</a></li>
</ul>

</div>
<!--/.well -->
</div>
<!--/span-->

<div class="span9">

<div class="well">

<form class="form-horizontal" action="#" th:action="@{/savelead}" th:object="${lead}" method="post">

<h4>New Lead</h4>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="firstName">First Name</label>
<div class="controls">
<input type="text" th:field="*{firstName}" placeholder="First Name"></input>
</div>
</div>

<div class="span4">
<label class="control-label" for="lastName">Last Name</label>
<div class="controls">
<input type="text" placeholder="Last Name" th:field="*{lastName}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="opportunityAmount">Opportunity Amount</label>
<div class="controls">
<input type="text" placeholder="Opportunity Amount" th:field="*{opportunityAmount}"></input>
</div>
</div>

<div class="span4">
<label class="control-label" for="converted">Converted</label>
<div class="controls">
<input type="checkbox" disabled="true" th:field="*{converted}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="email">Email</label>
<div class="controls">
<div class="input-prepend">
<span class="add-on"><i class="icon-envelope"></i></span>
<input type="text" placeholder="Email" th:field="*{email}"></input>
</div>
</div>
</div>

<div class="span4">
<label class="control-label" for="mobile">Mobile</label>
<div class="controls">
<input type="text" placeholder="Mobile" th:field="*{mobile}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="workPhone">Work Phone</label>
<div class="controls">
<input type="text" placeholder="Work Phone" th:field="*{workPhone}"></input>
</div>
</div>

<div class="span4">
<label class="control-label" for="fax">Fax</label>
<div class="controls">
<input type="text" placeholder="Fax" th:field="*{fax}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span8">
<label class="control-label" for="description">Description</label>
<div class="controls">

<textarea rows="4" cols="20" placeholder="Description" th:field="*{description}"></textarea>
</div>
</div>

</div>
</div>

<hr></hr>
<h5>Address</h5>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="city">City</label>
<div class="controls">

<input type="text" id="city" placeholder="City" th:field="*{address.city}"></input>
</div>
</div>

<div class="span4">
<label class="control-label" for="country">Country</label>
<div class="controls">
<input type="text" id="country" placeholder="country" th:field="*{address.country}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span4">
<label class="control-label" for="postcode">Post Code</label>
<div class="controls">

<input type="text" id="postcode" placeholder="Post Code" th:field="*{address.postcode}"></input>
</div>
</div>

<div class="span4">
<label class="control-label" for="state">State</label>
<div class="controls">
<input type="text" id="state" placeholder="State" th:field="*{address.state}"></input>
</div>
</div>
</div>
</div>

<div class="control-group">

<div class="row-fluid">
<div class="span8">
<label class="control-label" for="street">Street</label>
<div class="controls">

<textarea rows="4" cols="60" placeholder="Street" th:field="*{address.street}"></textarea>
</div>
</div>

</div>
</div>

<div class="form-actions">
<button type="submit" class="btn btn-primary">Save</button>
<button type="button" class="btn">Cancel</button>
</div>

</form>
</div>

</div>
<!--/span-->

</div>
<!--/row-->

<hr></hr>

<footer>
<p>&copy; Company 2012</p>
</footer>

</div>
<!--/.fluid-container-->

<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="../assets/js/jquery.js"></script>
<script src="../assets/js/bootstrap-transition.js"></script>
<script src="../assets/js/bootstrap-alert.js"></script>
<script src="../assets/js/bootstrap-modal.js"></script>

<script src="../assets/js/bootstrap-dropdown.js"></script>
<script src="../assets/js/bootstrap-scrollspy.js"></script>
<script src="../assets/js/bootstrap-tab.js"></script>
<script src="../assets/js/bootstrap-tooltip.js"></script>
<script src="../assets/js/bootstrap-popover.js"></script>
<script src="../assets/js/bootstrap-button.js"></script>

<script src="../assets/js/bootstrap-collapse.js"></script>
<script src="../assets/js/bootstrap-carousel.js"></script>
<script src="../assets/js/bootstrap-typeahead.js"></script>

</body>
</html>

 

Next step is to modify the guest controller and introduce a new savelead controller as shown in Listing 2

Listing 2 - SaveLeadController.java

package com.effectivcrm.controller;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

import com.effectivcrm.domain.Address;
import com.effectivcrm.domain.Lead;

@Controller
@Slf4j
public class SaveLeadController {

@ModelAttribute
public Lead initLead(){
Lead lead = new Lead();
lead.setAddress(new Address());

log.info("Lead = {}", lead);
log.info("Address = {}", lead.getAddress());

return new Lead();
}

@RequestMapping("/savelead")
public String saveLead(Lead lead){
log.info("Lead for saving = {}", lead);
return "createlead";
}

@RequestMapping(value="/createlead")
public String createLead(){
return "createlead";
}

}

 

Now the most important thing we introduce the domain objects. For this we need the Hibernate dependencies. These are added in the parent pom.xml. The domain object project is created by Maven quickstart archetype. The domain object Lead extends from PersistentObject class and also embeds an Address object. Note the use of nested object with Spring EL and Thmyeleaf in Listing 1. All source code is available in SVN.

Listing 3 - PeristentObject.java

/**
*
*/
package com.effectivcrm.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
import org.joda.time.DateTime;

/**
* @author Dhrubo
* @param
* @param
*
*/
@Audited
@MappedSuperclass
@EqualsAndHashCode(exclude={"createdBy", "lastModifiedBy", "createdDate", "lastModifiedDate", "deleted", "version"})
@ToString(exclude={"createdBy", "lastModifiedBy", "createdDate", "lastModifiedDate", "deleted", "version"})
public abstract class PersistentObject implements Serializable{

private static final long serialVersionUID = 1L;

@Getter @Setter
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Column(name = "id", updatable = false, nullable = false, unique = true)
private ID id;

@Getter @Setter
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "created_by", referencedColumnName = "user_id", nullable = false, updatable = false)
private U createdBy;

@Getter @Setter
@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name = "last_modified_by", referencedColumnName = "user_id", nullable = false, updatable = true)
private U lastModifiedBy;

@Getter @Setter
@Temporal(TemporalType.TIMESTAMP)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@Column(name = "created_date", nullable = false, updatable = false)
private DateTime createdDate;

@Getter @Setter
@Temporal(TemporalType.TIMESTAMP)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@Column(name = "last_modified_date", nullable = false, updatable = true)
private DateTime lastModifiedDate;

@Getter @Setter
@Column(name = "deleted")
private boolean deleted;

@Getter @Setter
@Version
@Column(name = "version")
private int version;

}

 

Listing 4 - User.java

package com.effectivcrm.domain;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
import org.joda.time.DateTime;

/**
* @author Dhrubo
*
*/
@Audited
@Entity
@Table(name = "t_user")
public class User implements Serializable{
private static final long serialVersionUID = 1L;

@Getter @Setter
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
@Column(name = "user_id", updatable = false, nullable = false, unique = true)
private String id;

@Getter @Setter
@Column(name="first_name" ,nullable=false , length=50)
private String firstName;

@Getter @Setter
@Column(name="last_name" ,nullable=false , length=50)
private String lastName;

@Getter @Setter
@Column(name="email",unique=true ,nullable=false, length=100)
private String email;

@Getter @Setter
@Column(name="password",unique=true ,nullable=false, length=100)
private String password;

@Getter @Setter
@Temporal(TemporalType.TIMESTAMP)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@Column(name = "created_date", nullable = false, updatable = false)
private DateTime createdDate;

@Getter @Setter
@Temporal(TemporalType.TIMESTAMP)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@Column(name = "last_modified_date", nullable = false, updatable = false)
private DateTime lastModifiedDate;

@Getter @Setter
@Deprecated
@Temporal(TemporalType.TIMESTAMP)
@Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
@Column(name = "activation_date", nullable = true, updatable = false)
private DateTime activationDate;

@Getter @Setter
@Column(name="deleted" ,nullable=false)
private boolean deleted;

@Getter @Setter
@Column(name="locked" ,nullable=false)
private boolean locked;

}

 

Listing 5 - Address.java

package com.effectivcrm.domain;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

import lombok.Getter;
import lombok.Setter;

/**
* @author Dhrubo
*
*/
@Embeddable
public class Address implements Serializable {
private static final long serialVersionUID = 1L;

@Getter @Setter
@Column(name="city", insertable=true , updatable=true, length=50)
private String city;

@Getter @Setter
@Column(name="country", insertable=true , updatable=true, length=50)
private String country;

@Getter @Setter
@Column(name="postcode", insertable=true , updatable=true, length=50)
private String postcode;

@Getter @Setter
@Column(name="state", insertable=true , updatable=true, length=50)
private String state;

@Getter @Setter
@Column(name="street", insertable=true , updatable=true, length=50)
private String street;

}

 

Listing 6 - Lead.java

package com.effectivcrm.domain;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Table;
import lombok.Getter;
import lombok.Setter;

import org.hibernate.envers.Audited;

/**
* The persistent class for the leads database table.
*
*/
@Entity
@Table(name = "t_lead")
@Audited
public class Lead extends PersistentObject {
private static final long serialVersionUID = 1L;

@Getter @Setter
@Column(name = "first_name", length = 100)
private String firstName;

@Getter @Setter
@Column(name = "last_name", length = 100)
private String lastName;

@Getter @Setter
@Column(name = "opportunity_amount")
private double opportunityAmount;

@Getter @Setter
@Column(name = "converted")
private boolean converted;

@Getter @Setter
@Column(name = "description", length = 250)
private String description;

@Getter @Setter
@Column(name = "email", length = 100)
private String email;

@Getter @Setter
@Column(name = "mobile", length = 30)
private String mobile;

@Getter @Setter
@Column(name = "work_phone", length = 30)
private String workPhone;

@Getter @Setter
@Column(name="fax")
private String fax;

@Getter @Setter
@Embedded
private Address address;

}

So whats next in part 3

  • Tiles integration
  • Internationalized messages
  • Validation
  • Spring Security integration
  • Introduction of service layer
  • Introduction of repository layer

    Part 3 - Introducing Project Lombok and thinking the dynamic form model

    We are at the verge of writing few beans and I personally hate those getter/setters toString hashCode, equals to be written for each one of them.I am also not too confident or knowleagble about tools like Spring ROO. So what option I have to save myself from these boring boilerplate clode. I can save some by using Project Lombok. Project lombok is available at - http://projectlombok.org/. I will not show here how to integrate it with STS, its straight forward and well documented on the product website.Do not forget to include it as a maven dependency in the parent project to use it in child projects and save some time.

    Dynamic form

    Creating the dynamic form will be an excellent thing. We have lot of meta data to do that in the domain objects. We can even keep the form meta information in xml or database. The Spring Thymeleaf dialect already has excellent support for forms. For the moment we will put the dynamic form into parking lot and build forms using Thymeleaf. In future if this really becomes an absolute necessary we will revisit the topic.

    Saturday, September 8, 2012

    Part 3 - Enhancing the view layer - Creating the screen prototypes & dyna form

    We now have a more or less stable version 0.0.2. You can find it in tagged version 0.0.2 on SVN.

    Now I will try to create the prototypes for our views. We can do that creating new Thymeleaf templates. One big advantage of Thymeleaf is that it supports natural templating so you can view it easily on the browser offline i.e without running it on Tomcat. Unfortunately we have already lost that advantage as our css,images, and js are packed in jar. But it should not be a big deterrent to our prototyping effort. Its a lightweight application and we can run it on Tomcat and view the changes quickly.

    I will also sacrifice the natural templating feature of Thymeleaf for another reason. I want flexible forms driven by meta data --- Hibernate annotations of the domain objects or may be database driven forms. Hence I will extend Thymeleaf to support additional dialects to create a new tag -- "datatable". Also this form will be driven by custom actions / buttons all configurable. The new createlead.html is ready and available on SVN. It is shown in listing below:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">

    <head th:include="common :: headerFragment">

    <title >effectiv:Home</title>

    </head>

    <body>

    <div class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">

    <div class="container-fluid">
    <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>
    <a class="brand" href="#">Project name</a>
    <div class="nav-collapse collapse">

    <p class="navbar-text pull-right">
    Logged in as <a href="#" class="navbar-link">Username</a>
    </p>
    <ul class="nav">
    <li class="active"><a href="#">Home</a></li>
    <li><a href="#about">About</a></li>
    <li><a href="#contact">Contact</a></li>

    </ul>
    </div><!--/.nav-collapse -->
    </div>
    </div>
    </div>

    <div class="container-fluid">
    <div class="row-fluid">
    <div class="span3">

    <div class="well sidebar-nav">
    <ul class="nav nav-list">
    <li class="nav-header">Sidebar</li>
    <li class="active"><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>

    <li class="nav-header">Sidebar</li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>

    <li><a href="#">Link</a></li>
    <li class="nav-header">Sidebar</li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    <li><a href="#">Link</a></li>
    </ul>

    </div><!--/.well -->
    </div><!--/span-->

    <div class="span9">

    <div class="well">

    <form class="form-horizontal">

    <legend>New Lead</legend>

    <div class="control-group">

    <div class="row-fluid">
    <div class="span4">
    <label class="control-label" for="inputEmail">Email</label>
    <div class="controls">
    <input type="text" id="inputEmail" placeholder="Email"></input>
    </div>
    </div>

    <div class="span4">
    <label class="control-label" for="firstName">First Name</label>
    <div class="controls">
    <input type="text" id="firstName" placeholder="First Name"></input>
    </div>
    </div>
    </div>
    </div>

    <div class="control-group">

    <div class="row-fluid">
    <div class="span4">
    <label class="control-label" for="inputEmail">Email</label>
    <div class="controls">
    <input type="text" id="inputEmail" placeholder="Email"></input>
    </div>
    </div>

    <div class="span4">
    <label class="control-label" for="firstName">First Name</label>
    <div class="controls">
    <input type="text" id="firstName" placeholder="First Name"></input>
    </div>
    </div>
    </div>
    </div>

    </form>
    </div>

    </div><!--/span-->
    </div><!--/row-->

    <hr></hr>

    <footer>
    <p>&copy; Company 2012</p>
    </footer>

    </div><!--/.fluid-container-->

    <!-- Le javascript
    ================================================== -->
    <!-- Placed at the end of the document so the pages load faster -->
    <script src="../assets/js/jquery.js"></script>
    <script src="../assets/js/bootstrap-transition.js"></script>
    <script src="../assets/js/bootstrap-alert.js"></script>
    <script src="../assets/js/bootstrap-modal.js"></script>

    <script src="../assets/js/bootstrap-dropdown.js"></script>
    <script src="../assets/js/bootstrap-scrollspy.js"></script>
    <script src="../assets/js/bootstrap-tab.js"></script>
    <script src="../assets/js/bootstrap-tooltip.js"></script>
    <script src="../assets/js/bootstrap-popover.js"></script>
    <script src="../assets/js/bootstrap-button.js"></script>

    <script src="../assets/js/bootstrap-collapse.js"></script>
    <script src="../assets/js/bootstrap-carousel.js"></script>
    <script src="../assets/js/bootstrap-typeahead.js"></script>

    </body>
    </html>

    More on this dynamic form in the next edition, stay tuned.

     

    Friday, September 7, 2012

    Part 2 - Enhancing the setup

    As you can seen modularity has a price. Our application will be divided into multiple Maven projects / modules. Lets say in development I change 2-3 modules, then I need to build each one of them to test. This can be irritating and time wasting. Also I am a lazy developer, I will want to build them as one go. Hence I will now create a multi-module Maven build parent - child. I will just build the parent to build all my childs, including the aggregator ecrm project, which we will essentially deploy.

    So we create a maven quick start archetype project. You can turn this into parent project as shown in Listing 1. Also I will refactor the pom in ercm project by moving dependencies and properties to the parent. Similarly view/pom.xml is modified to treat it as a child project.

    Listing 1 - parent/pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ecrm</artifactId>
    <packaging>war</packaging>

    <name>ecrm</name>
    <url>http://maven.apache.org</url>

    <!-- ~~~~~~~~~~~~ -->
    <!-- DEPENDENCIES -->
    <!-- ~~~~~~~~~~~~ -->
    <dependencies>
    <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>view</artifactId>
    <version>${project.version}</version>
    </dependency>
    </dependencies>

    <build>
    <finalName>${project.artifactId}</finalName>
    </build>

    <parent>
    <groupId>com.effectivcrm</groupId>
    <artifactId>parent</artifactId>
    <version>0.0.2</version>
    </parent>
    </project>

     

    Listing 2 - ecrm/pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ecrm</artifactId>
    <packaging>war</packaging>

    <name>ecrm</name>
    <url>http://maven.apache.org</url>

    <!-- ~~~~~~~~~~~~ -->
    <!-- DEPENDENCIES -->
    <!-- ~~~~~~~~~~~~ -->
    <dependencies>
    <dependency>
    <groupId>${project.groupId}</groupId>
    <artifactId>view</artifactId>
    <version>${project.version}</version>
    </dependency>
    </dependencies>

    <build>
    <finalName>${project.artifactId}</finalName>
    </build>

    <parent>
    <groupId>com.effectivcrm</groupId>
    <artifactId>parent</artifactId>
    <version>0.0.2</version>
    </parent>
    </project>

     

    Listing 3 - view/pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>view</artifactId>
    <packaging>jar</packaging>

    <name>view</name>
    <url>http://maven.apache.org</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

    </dependencies>

    <parent>
    <groupId>com.effectivcrm</groupId>
    <artifactId>parent</artifactId>
    <version>0.0.2</version>
    </parent>

    </project>

    Update - separating the controller

    Following on our trail to create modular application, its time to move the controller to a different module / project. Thanks to maven this is very easy we can create another project with quickstart archetype named controller.Now we move the controller Java class to this project under com.effectivcrm.controller package. Also I will tweak the controller/pom.xml to include it as a child project. We also introduce a spring configuration file spring-controller-config.xml to include module specific Spring configuration for the controllers. Since the controller package scanning is moved to the new Spring configuration it has to be removed from the spring-web-config.xml. I will rename that to view/spring-view-config. Last but not the least we need to include the new project as parent module and depdency in ecrm project the aggregator.

    Update - fixing the welcome page

    Last but not the least we will solve the problem with welcome file and redirect to first / guest page. Unfortunately there is problem with Tomcat (or is this a feature? I do not know). So what I do is I will delete this index.jsp and remove the welcome file entry in web.xml. I will not add an entry in the controller to handle request mapping to / and pass it to the sign in page. The modifided code base is now available in the trunk.

    Update - The missing log4j configuration

    Well I missed the log4j configuration file. Actually my Tomcat had one so I had no problem. Note that the log4j is not residing inside my war / classpath. I have externalized it to load from a specific location inside Tomcat. This is probably not the world's best log4j.xml. I will progressively enhance it as we move forward to cater to different needs.  I have uploaded the same in the conf folder of svn/trunk.

    Update - resources from classpath

    I also want the resources to be loaded in a modular way. Hence I moved them to src/main/resources/META-INF/assets folder of view project. Accordingly I have changed the resource configuration in sping-view-config.xml file as shown in the snippet below:

     

    <mvc:resources location="classpath:/META-INF/assets/img/" mapping="/assets/img/" />
    <mvc:resources location="classpath:/META-INF/assets/css/" mapping="/assets/css/
    " />
    <mvc:resources location="classpath:/META-INF/assets/js/" mapping="/assets/js/**" />

    SVN Location - http://code.google.com/p/spring-modular/source/browse/