Saturday, March 19, 2011

Spring Internationalization And Themes

Hey...

Most of enterprise applications should support internationalization and themes. I mean they should prepare users to change the application language and also select a themplate or even customize it.Spring has a powerful features to support them. Here I am going to take a sample of spring internationalization and themes.

Application Specification
The sample application is an extension of spring security sample. A user can login to it then a list of users will be displayed and if he or she has administrator or power user role, can change selected user's current theme and language in edit mode. Take a look to the following picture.
Image 1

System has 3 themes:
  • Default(Gray) Theme: Gray background color
  • Green Theme: Green background color
  • Blue Theme: Blue background color
and 2 language available:
  • Default(United States)
  • Farsi
If user changes one of the available theme and language for selected user and if the selected user and current user are the same, the changes will be shown after saving othervise whenever the selected user login to system, he or she will see the changes. Take a look to following picture:
Image 2: after selecting blue theme and Farsi language


I added two more extera fields in User Entity (refer to spring security post). theme and locale:
@Entity
@Table(name = "tb_user")
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "username")
    String username;

    @Column(name = "password", updatable = false)
    String password;

    @Column(name = "theme")
    String theme;

    @Column(name = "locale")
    String locale;

    @ManyToMany(targetEntity = Role.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
    @JoinTable(name = "tb_user_role", joinColumns = {@JoinColumn(name = "user_id")}, inverseJoinColumns = {@JoinColumn(name = "role_id")})
    @Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
    List<Role> roleList;

    @Column(name = "account_non_expired")
    boolean accountNonExpired;

    @Column(name = "account_non_locked")
    boolean accountNonLocked;

    @Column(name = "credentials_non_expired")
    boolean credentialsNonExpired;

    @Column(name = "enabled")
    boolean enabled;

...

So I will find the current theme and current language by current user. But if current user is ananymouse, the default theme and default language(United States) will be assumed.

Now it's the time to explain about spring internationalization and themes.

Spring Internationalization
First we should prepare some different languages property files with the same master name with language brief name in continue. For example if the master name is messages, for United States: messages_en and for Farsi: messages_fa. Then we should place the keys and their values in them. Take a look this:

messages_en:
user.name=name
user.username=username
user.password=password
user.theme=user custom theme
user.locale=user language
user.accountNonExpired=account non expired
user.accountNonLocked=account non locked
user.credentialsNonExpired=credentials non expired
user.enabled=enabled
user.authorities=user authorities

welcome=welcome

save=save
back=back
edit=edit
view=view details
add=insert
delete=delete
search=search

login=login
logout=logout

messages_fa:
user.name=نام کاربر
user.username=نام کاربري
user.password=کلمه عبور
user.theme=قالب جاري کاربر
user.locale=زبان جاري کاربر
user.accountNonExpired=حساب کاربری باطل نشده
user.accountNonLocked=حساب کاربری قفل نشده
user.credentialsNonExpired=اعتبار نامه کابر باطل نشده
user.enabled=فعال
user.authorities=اجازه های دسترسی کاربر


welcome=خوش آمدید


save=ذخیره
back=بازگشت
edit=ویرایش
view=نمایش جزئیات
add=افزودن
delete=حذف
search=جستجو


login=ورود
logout=خروج
and so on.
The next step is spring configuration. I make an xml file called internationalization-config.xml and import it in major spring configuration(spring-servlet.xml). Here is the configuration:
<?xml version="1.0" encoding="UTF-8"?>

<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" xmlns:tx="http://www.springframework.org/schema/tx"
             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://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <beans:bean id="messageSource"
                class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <beans:property name="basename" value="classpath:messages"/>
        <beans:property name="defaultEncoding" value="UTF-8"/>
    </beans:bean>
    <beans:bean id="localeResolver"
    class="com.internationalization.CustomLocaleResolver">
    <beans:property name="defaultLocale" value="en"/>
</beans:bean>
</beans:beans>
I should define a bean with "messageSource' id(the name should be this. it is a reserved id name) type of ReloadableResourceBundleMessageSource class. I choose resource bundle source via this bean. I determine the master name of the property file is messages with UTF-8 encoding.
Then I should define the localeResolver to determine current locale so I prepare CustomLocaleResolver for that. This class will find the current user and returns its current locale otherwise returns the default locale(United States). Every localeResolver classes should be instance of org.springframework.web.servlet.LocaleResolver interface. But there is a useful abstract class called "AbstractLocaleResolver" is an instance of LocaleResolver interface. It has good properties like "defaultLocale", so I extend CustomLocaleResolver class from AbstractLocaleResolver class. Here is the code:
public class CustomLocaleResolver extends AbstractLocaleResolver {

    public Locale resolveLocale(HttpServletRequest httpServletRequest) {
        User user = SecurityUtils.getCurrentUser();
        if (user != null && user.getLocale() != null && !user.getLocale().equals("")) {
            return new Locale(user.getLocale());
        } else {
            return Locale.US;
        }
    }

    public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
    }
}
SecurityUtils is an utility class which returns the current user from spring security. Here is the code:
public class SecurityUtils {
    public static User getCurrentUser(){
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        User user = null;
        if (principal instanceof User) {
            user = (User) principal;
        }
        return user;
    }
}
after configuration I can use it in the pages. Spring has a tag to do it.
<spring:message code="key"/>
For example in user-edit.jsp I use this tag:
<form:form id="userForm">
    <form:hidden path="user.id"/>
    <table>
        <tr>
            <td><spring:message code="user.name"/> </td>
            <td><form:input path="user.name"/></td>
        </tr>
        <tr>
            <td><spring:message code="user.username"/> </td>
            <td><form:input path="user.username" autocomplete="false"/></td>
        </tr>
        <tr>
            <td><spring:message code="user.password"/> </td>
            <td><form:password showPassword="false" path="user.password" autocomplete="false"/></td>
        </tr>
        <tr>
            <td><spring:message code="user.theme"/> </td>
            <td><form:select path="user.theme">
                <form:option value="default" label="default"></form:option>
                <form:option value="green" label="green"></form:option>
                <form:option value="blue" label="blue"></form:option>
            </form:select></td>
        </tr>
...

</form:form>
another point is direction. Most middle east countries read from right to left(against Europeans and Americans). So I should change the main div direction when the current user language is Farsi. I do it in /WEB-INF/layout/view.jsp
<security:authentication property="principal" var="principal"/>
<c:set var="anonymousUser" value="${false}"/>
<security:authorize access="isAnonymous()">
    <c:set var="anonymousUser" value="${true}"/>
</security:authorize>
<c:choose>
   <c:when test="${!anonymousUser and principal.locale=='fa'}">
      <div class="mainDiv" dir="rtl"></c:when>
   <c:otherwise>
      <div class="mainDiv"></c:otherwise> 

</c:choose>
        <security:authorize access="isAuthenticated()">
            <a href="/j_spring_security_logout"><spring:message code="logout"/> </a><br/> <spring:message code="welcome"/> <security:authentication
                property="principal.name"/>
        </security:authorize>
        <tiles:insertAttribute name="body"/>
    </div>
I handle it by c:choose. When current user is ananymous or has United States locale, the main div direction should be left to right otherwise should be right to left.
spring:message looks like fmt:message jstl tag. we can set it to variable. Take a look to this:
<spring:message key="user.name" var="name"/>
${name}
As you can see in above sample you can have the variable in page scope and use it in EL.

Spring Theme
Spring Theme looks like Spring Internationalization both in configuration and usage.
First I should create an xml file called theme-config.xml for configuration and import it in main configuration file(spring-servlet.xml).
theme-config.xml

<?xml version="1.0" encoding="UTF-8"?>

<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" xmlns:tx="http://www.springframework.org/schema/tx"
             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://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <beans:bean id="themeSource" class="org.springframework.ui.context.support.ResourceBundleThemeSource">
        <beans:property name="basenamePrefix" value="theme-" />
    </beans:bean>

    <beans:bean id="themeResolver" class="com.theme.CustomThemeResolver">
        <beans:property name="defaultThemeName" value="default"/>
    </beans:bean>
</beans:beans>
I should define a bean with "themeSource' id(the name should be this. it is a reserved id name) type of ResourceBundleThemeSource class. I choose resource bundle source via this bean. I name the base name prefix to "theme-". So any property file which starts with "theme-" could be theme resource bundle. For example "theme-blue"
Then I should define the themeResolver to determine current theme so I prepare CustomThemeResolver for that. This class will find the current user and returns its current theme otherwise returns the default theme(default). Every localeResolver classes should be instance of org.springframework.web.servlet.ThemeResolver interface. But there is a useful abstract class called "AbstractThemeResolver" is an instance of ThemeResolver interface. It has good properties like "defaultThemeName", so I extend CustomThemeResolver class from AbstractThemeResolver class. Here is the code:
public class CustomThemeResolver extends AbstractThemeResolver {
    public static final String DEFAULT_THEME = "default";
    public static final String DEFAULT_GREEN = "green";
    public static final String DEFAULT_BLUE = "blue";

    public String resolveThemeName(HttpServletRequest request) {
        User user = SecurityUtils.getCurrentUser();
        if (user != null && user.getTheme() != null && !user.getTheme().equals("")) {
            return user.getTheme();
        } else {
            return DEFAULT_THEME;
        }
    }

    public void setThemeName(HttpServletRequest request, HttpServletResponse response, String s) {
    }
}
If I want to have 3 themes so I should have 3 property files.

theme-default.properties
css=/themes/default/main.css

theme-green.properties
css=/themes/green/main.css

theme-blue.properties
css=/themes/blue/main.css
these are the path of css classes. Any theme property should determine the path of the css file in css key.
As is shown in above there should be 3 folders in theme folder including: default, green and blue. Each folder should contains a main.css file.

/theme/default/main.css
.mainDiv{
    background-color:lightgray;
    padding:2px;
    margin:2px;
}

/theme/green/main.css
.mainDiv{
    background-color:lightgreen;
    padding:2px;
    margin:2px;
}

/theme/blue/main.css
.mainDiv{
    background-color:lightblue;
    padding:2px;
    margin:2px;
}

Now it's the time to use it in pages. Spring has a tag which return the value of the current theme key
<spring:theme code='key'/>
For example in /WEB-INF/layout/view.jsp file:
<html>
<head>
    <link rel="stylesheet" type="text/css" href="<spring:theme code='css'/>"/>
</head>
<body>
<security:authentication property="principal" var="principal"/>
<c:set var="anonymousUser" value="${false}"/>
<security:authorize access="isAnonymous()">
    <c:set var="anonymousUser" value="${true}"/>
</security:authorize>
<c:choose>
   <c:when test="${!anonymousUser and principal.locale=='fa'}">
      <div class="mainDiv" dir="rtl"></c:when>
   <c:otherwise>
      <div class="mainDiv"></c:otherwise>
   </c:choose>
        <security:authorize access="isAuthenticated()">
            <a href="/j_spring_security_logout"><spring:message code="logout"/> </a><br/> <spring:message code="welcome"/> <security:authentication
                property="principal.name"/>
        </security:authorize>
        <tiles:insertAttribute name="body"/>
    </div>
</body>
</html>
As you can see the main div uses mainDiv class. The css file which defines this class is inclued in the head. But not the exactly name of the css. It is determined from spring:theme tag.

OK. I think it is the time that you yourselves should take tour in the application. You can download source code from here. The needed jar files are not available in it. But the jar files are the same as older project. You can use Spring WebFlow sample jar files. there is a mysql database script available in [Project Root]/db/intermsg.sql. You can use it to restore the database in your mysql server. after running the application the admin user specification is:
username: administrator
password: 123456
The datasouce properties is available in [Project Root]/src/database.properties.


all rights reserved by Mostafa Rastgar and Programmer Assistant weblog

2 comments:

Yadu Bhushan said...

Very nice and Informative....

rainhardmabus said...

Sloty Casino Hotel, New York - MapyRO
Overview. Sloty 광주광역 출장마사지 Casino Hotel, New York. 고양 출장마사지 MapyRO location. 전주 출장샵 Location. 3131 강릉 출장샵 South Main Street. Mapyro. (212) 김제 출장마사지 751-4280