TERASOLUNAServer Framework For Java Development Guideline
TERASOLUNAServer Framework For Java Development Guideline
NTT DATA
CONTENTS
1 In the Beginning 3
1.1 Terms of Use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 This document covers the following . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Target readers of this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.4 Structure of this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.5 Reading this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.6 Tested environments of this document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.7 Criteria based mapping of guideline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.8 Change Log . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 Architecture Overview 37
2.1 Stack of TERASOLUNA Server Framework for Java (5.x) . . . . . . . . . . . . . . . . . . . . . 37
2.2 Overview of Spring MVC Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.3 First application based on Spring MVC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.4 Application Layering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
3 Application Development 93
3.1 Create Web application development project . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
3.2 Domain Layer Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
3.3 Implementation of Infrastructure Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
3.4 Implementation of Application Layer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
3.5 Build development project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
8 Messaging 1621
8.1 Sending E-mail (SMTP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1621
8.2 JMS(Java Message Service) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1651
10 Tutorials 2027
10.1 Tutorial (Todo Application) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2027
10.2 Tutorial (Todo Application REST) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2127
10.3 Session tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2192
ii
10.4 Spring Security Tutorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2256
iii
iv
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: If there are any mistakes in the contents or any comments related to the contents, please raise them at
Issues on Github.
1
3
In the Beginning
In order to use this document, you are required to agree to abide by the following terms. If you do not agree with
the terms, you must immediately delete or destroy this document and all its duplicate copies.
1. Copyrights and all other rights of this document shall belong to NTT DATA or third party possessing such
rights.
2. This document may be reproduced, translated or adapted, in whole or in part for personal use. However,
deletion of the terms given on this page and copyright notice of NTT DATA is prohibited.
3. This document may be changed, in whole or in part for personal use. Creation of secondary work using
this document is allowed. However, “Reference document: TERASOLUNA Server Framework for Java
(5.x) Development Guideline” or equivalent documents may be mentioned in created document and its
duplicate copies.
4. Document and its duplicate copies created according to Clause 2 may be provided to third party only if
these are free of cost.
5. Use of this document and its duplicate copies, and transfer of rights of this contract to a third party, in
whole or in part, beyond the conditions specified in this contract, are prohibited without the written consent
of NTT Data.
6. NTT DATA shall not bear any responsibility regarding correctness of contents of this document, warranty of
fitness for usage purpose, assurance for accuracy and reliability of usage result, liability for defect warranty,
and any damage incurred directly or indirectly.
7. NTT DATA does not guarantee the infringement of copyrights and any other rights of third party through
this document. In addition to this, NTT DATA shall not bear any responsibility regarding any claim (Includ-
ing the claims occurred due to dispute with third party) occurred directly or indirectly due to infringement
of copyright and other rights.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Registered trademarks or trademarks of company name and service name, and product name of their respective
companies used in this document are as follows.
• All other company names and product names are the registered trademarks or trademarks of their respective
companies.
4 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
This guideline provides best practices to develop highly maintainable Web applications using full stack framework
focussing on Spring, Spring MVC and JPA, MyBatis.
This guideline helps to proceed with the software development (mainly coding) smoothly.
This guideline is written for the architects and programmers having software development experience and knowl-
edge of the following.
• Knowledge of SQL
In order to check whether the readers have basic knowledge of Spring Framework, refer to Spring Framework
Comprehension Check. It is recommended to study the following books if one is not able to answer 40% of the
comprehension test.
•Architecture Overview Overview of Spring MVC and basic concepts of TERASOLUNA Server Frame-
work for Java (5.x) is explained.
•Application Development Knowledge and methods for application development using TERASOLUNA
Server Framework for Java (5.x) are explained.
•Tutorials Experience in application development using TERASOLUNA Server Framework for Java (5.x)
through simple application development.
•Appendix(Know How) Describing the additional information when TERASOLUNA Server Framework
for Java (5.x) is being used.
Use this tutorial to study the details of application development in “Application Development”.
Since the knowhow for development is explained using Spring MVC in “Implementation of Application Layer”,
it is recommended to read it again and again several times.
One can get a better understanding by studying “Tutorials” once again after reading this chapter.
It is strongly recommended that all the developers who use TERASOLUNA Server Framework for Java
(5.x) read it.
The technical leader understands all the contents and checks the type of policy to be decided in the project.
Note: If you do not have sufficient time, first go through the following.
2. Application Layering
4. Application Development
6 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
6. Input Validation
For tested environments of contents described in this guideline, refer to “Tested Environment”.
The chapter 4 of this guideline is structured functionality wise. This section shows a mapping from a point of
view other than functionality. It indicates which part of guideline contains which type of content.
Using OWASP Top 10 for 2013 as an axis, links to explanation of functionalities related to security have been
given
8 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
A1 Injection
• Input check validation for security
(Shows how to validate input values)
A2 Broken Authentication and Session Manage-
• Session Management
ment
• Authentication
A5 Security Misconfiguration
• Logging(Mention about message contents of log)
• Method to display system exception code on
screen(Mention about message output at the time
of system exception)
10 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
12 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
General Correction of errors in the guideline (typo mistakes and simple de-
scription errors)
Description details modified
• For details of modification, refer 5.1.0 Issue list (improve-
ment).
14 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
16 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
18 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
20 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Stack of TERASOLUNA Updated the OSS version(Spring IO Platform version) to protect se-
Server Framework for Java curity vulnerability
(5.x) • Spring IO Platform version updated to 1.1.3.RELEASE
• Spring Framework version updated to 4.1.7.RELEASE
(CVE-2015-3192)
• JSTL version updated to 1.2.5 (CVE-2015-0254)
Updated the OSS version by the Spring IO Platform version update
• Updated the OSS version to be used. For update details, refer
to Migration guide of version 5.0.1
Improved the description (guideline#1148)
• Added the description of
terasoluna-gfw-recommended-dependencies,terasolun
and terasoluna-gfw-parent
• Modified the description for some project
• Added the illustration to indicate project dependencies
22 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
24 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
26 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
28 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
30 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
32 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
34 1 In the Beginning
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Stack of TERASOLUNA Updated the OSS version in accordance with bug fixes.
Server Framework for Java • GroupId (org.springframework ) updated to
(5.x) 3.2.10.RELEASE from 3.2.4.RELEASE
• GroupId (org.springframework.data )/Artifac-
tId(spring-data-commons ) updated to 1.6.4.RE-
LEASE from 1.6.1.RELEASE
• GroupId (org.springframework.data )/Artifac-
tId(spring-data-jpa ) updated to 1.4.3.RELEASE from
1.4.1.RELEASE
• GroupId (org.aspectj ) updated to 1.7.4 from 1.7.3
• Deleted GroupId (javax.transaction )/ArtifactId(jta
)
Japanese version Change how to create following projects to be carried out from mvn
archetype:generate
• First application based on Spring MVC
• Tutorial (Todo Application)
• Tutorial (Todo Application)
36 1 In the Beginning
37
Architecture Overview
Software Framework being used in TERASOLUNA Server Framework for Java (5.x) is not a proprietary Frame-
work but a combination of various OSS technologies around Spring Framework.
Libraries which constitute TERASOLUNA Server Framework for Java (5.x) are as follows:
DI Container
MVC Framework
O/R Mapper
• MyBatis 3.3
• JPA2.1
Note: To be precise MyBatis is a “SQL Mapper”, but it is classified as “O/R Mapper” in this guidelines.
Warning: Every project must not adopt JPA. For situations in which table design has been done and “Most
of the tables are not normalized”, “The number of columns in the table is too large” etc, use of JPA is difficult.
Furthermore, this guideline does not explain the basic usage of JPA. Hence, it is pre-requisite to have JPA
experience people in the team.
38 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
View
Security
Tip: In addition to providing a mechanism of authentication and authorization in Spring Security 3.2, the mech-
anism has been enhanced to protect a Web application from malicious attackers.
• CSRF Countermeasures
Validation
Logging
Common Library
• https://github.com/terasolunaorg/terasoluna-gfw
dependencies resolved and OSS version to be used in the TERASOLUNA Server Framework for Java (5.x) is
following the rule of Spring IO platform definition.
40 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
3. Libraries which are not dependent on Common Library, but recommended in case of application
development using TERASOLUNA Server Framework for Java (5.x).
4. Libraries that are supported by Spring IO platform, but library that relies individually.
42 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
5. Library versions that are applied in the Spring IO platform is a Beta or RC (Release Candidate)
(Library that explicitly specify the GA version at TERASOLUNA Server Framework for Java (5.x))
Common Library includes + alpha functionalities which are not available in Spring Ecosystem or other depen-
dent libraries included in TERASOLUNA Server Framework for Java (5.x). Basically, application development
is possible using TERASOLUNA Server Framework for Java (5.x) without this library but “convenient to have”
kind of existence. With the default settings, provided two blank projects, Blank project of multi-project and Blank
project of single-project, contains built-in Common Library as shown in the following listing.
3. With the default settings of Common Library, when MyBatis3 is used for data access.
4. With the default settings of Common Library, when JPA is used for data access.
The project which does not contain the Java source code, only defines library dependencies.
Note: Excluding exceptions, a project assigned with “dependencies” at the end of project name exists in common
library. (For example, terasoluna-gfw-common-dependencies etc corresponding to terasoluna-gfw-common)
In these projects, since a dependency definition for OSS library recommended for use is provided along with
dependency definition for common library, it is recommended to add it to pom.xml as a dependency, for the
project assigned with “dependencies” while using a common library.
terasoluna-gfw-common
terasoluna-gfw-string
terasoluna-gfw-codepoints
terasoluna-gfw-validator
46 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
terasoluna-gfw-jodatime
terasoluna-gfw-web
terasoluna-gfw-web-jsp
48 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
terasoluna-gfw-security-web
Spring’s web MVC framework is, like many other web MVC frameworks, request-driven, designed around
a central Servlet that dispatches requests to controllers and offers other functionality that facilitates the
development of web applications. Spring’s DispatcherServlet however, does more than just that. It is
completely integrated with the Spring IoC container and as such allows you to use every other feature that
Spring has.
The processing flow of Spring MVC from receiving the request till the response is returned is shown in the
following diagram.
50 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
5. Controller executes the business logic, sets the processing result in Model and returns the logical
name of view to HandlerAdapter.
6. DispatcherServlet dispatches the task of resolving the View corresponding to the View name to
ViewResolver. ViewResolver returns the View mapped to View name.
Among the components explained previously, the extendable components are implemented.
Implementation of HandlerMapping
Usually org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
is used. This class reads @RequestMapping annotation from the Controller and uses the method of
Controller that matches with URL as Handler class.
Implementation of HandlerAdapter
Usually org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
is used. RequestMappingHandlerAdapter class calls the method of handler class (Controller)
selected by HandlerMapping. In Spring 3.1, this class is also configured by default throught
<mvc:annotation-driven>.
Implementation of ViewResolver
Classes that implement ViewResolver provided by Spring framework and dependent libraries are shown below.
• org.springframework.web.servlet.view.InternalResourceViewResolver is used,
• org.springframework.web.servlet.view.tiles3.TilesViewResolver
• org.springframework.web.servlet.view.BeanNameViewResolver
Thereby, it is required to use different viewResolver based on the type of the View that needs to be returned.
When View of multiple types is to be handled, multiple definitions of ViewResolver are required.
52 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
A typical example of using multiple ViewResolver is the screen application for which file download process
exists.
For screen (JSP), View is resolved using InternalResourceViewResolver and for File download View
is resolved using BeanNameViewResolver.
For details, refer File Download.
Implementation of View
Classes that implement View provided by Spring framework and its dependent libraries are shown below.
View changes with the type of response to be returned. When JSP is to be returned,
org.springframework.web.servlet.view.JstlView is used. When View not provided by
Spring framework and its dependent libraries are to be handled, it is necessary to extend the class in which View
interface is implemented. For details, refer File Download.
54 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Before entering into the advanced usage of Spring MVC, it is better to understand Spring MVC by actually trying
hands-on web application development using Spring MVC.
2.3.1 Prerequisites
The description of this chapter has been verified on the following environment. (For other environments, replace
the contents mentioned here appropriately)
Classification Product
OS Windows 7
JVM Java 1.8
IDE Spring Tool Suite 3.6.4.RELEASE (Hereafter referred as [STS])
Build Tool Apache Maven 3.3.9 (Hereafter referred as [Maven])
Application Server Pivotal tc Server Developer Edition v3.1 (enclosed in STS)
Web Browser Google Chrome 46.0.2490.80 m
Note: To connect the internet via proxy server, STS Proxy settings and Maven Proxy settings are required for the
following operations.
Select the archetype created project from STS menu [File] -> [Import] -> [Maven] -> [Existing Maven Projects]
-> [Next].
Click on [Finish] by selecting C:\work\helloworld in Root Directory and selecting pom.xml of helloworld
in Projects.
To understand the configuration of Spring MVC, the generated Spring MVC configuration file
(src/main/resources/META-INF/spring/spring-mvc.xml) is described briefly.
56 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/s
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-u
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/sp
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<!-- (1) Enables the Spring MVC @Controller programming model -->
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalA
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
58 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!-- (3) Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-I
<!-- Settings View Resolver. -->
<mvc:view-resolvers>
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<bean class="org.springframework.security.web.servlet.support.csrf.CsrfRequestData
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataVal
</util:list>
</constructor-arg>
</bean>
</beans>
Define the package which will be target of searching components used in Spring MVC.
(2)
(3)
Tip: <mvc:view-resolvers> element is a XML element that added from Spring Framework
4.1. By using <mvc:view-resolvers> element, it is possible to define ViewResolver simply.
The definition example of using the conventional <bean> element is shown below.
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResol
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
package com.example.helloworld.app.welcome;
import java.text.DateFormat;
import java.util.Date;
60 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller // (4)
public class HomeController {
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = {RequestMethod.GET, RequestMethod.POST}) // (5)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
It gets executed when the HTTP method is GET or POST and the Resource is (or request URL) is “/”.
(5)
(6)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Hello world!</h1>
<p>The time on the server is ${serverTime}.</p> <%-- (8) --%>
</div>
</body>
</html>
62 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Right click “helloworld” project in STS, and start helloworld project by executing “Run As” -> “Run On Server”
-> “localhost” -> “Pivotal tc Server Developer Edition v3.0” -> “Finish”.
Enter “http://localhost:8080/helloworld/” in browser to display the following screen.
Note: Since tc Server internally uses the Tomcat, it is possible to choose below two versions in the STS.
• tomcat-7-0.57.A.RELEASE
If you want to switch the Tomcat to be used, change the [Version] field of ts Server by opening the [Edit Server
Runtime Environment] dialog box. Java (JRE) version can also be changed from this dialog box.
Lets go ahead and create a simple application. It is a typical eco application in which message will be displayed if
name is entered in the text field as given below.
64 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
package com.example.helloworld.app.echo;
import java.io.Serializable;
Create a Controller
package com.example.helloworld.app.echo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("echo")
public class EchoController {
@ModelAttribute // (1)
public EchoForm setUpEchoForm() {
EchoForm form = new EchoForm();
return form;
}
@RequestMapping // (2)
public String index(Model model) {
return "echo/index"; // (3)
}
66 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Add @ModelAttribute annotation to the method. Return value of such a method is automatically
added to the Model.
Attribute name of the Model can be specified in @ModelAttribute, but the class name with the
first letter in lower case is the default attribute name. In this case it will be “echoForm”. This attribute
name must match with the value of modelAttribute of form:form tag.
(2) When nothing is specified in value attribute of @RequestMapping annotation at the method
level, it is mapped to @RequestMapping added at class level. In this case, index method is
called, if “<contextPath>/echo” is accessed.
When nothing is set in method attribute, mapping is done for any HTTP method.
(4) Since “hello” is specified in value attribute and RequestMethod.POST is specified in method
attribute of the @RequestMapping annotation method, if “<contextPath>/echo/hello” is accessed
with POST method, hello method is called.
Note: The value specified in the method attribute of @RequestMapping annotation is generally varied by
how the data transmitted from the client.
• POST method in case of storing data to the server (in case of updating process).
• GET method or unspecified (any method) in case of not storing data to the server (in case of referring
process).
In echo application,
• index method is not going to save data to the server, it is unspecified (any method)
• hello method is going to save data into Model object, it is POST method
Finally create JSP for input screen and output screen. Each file path must match with View name as follows.
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<%-- (1) --%>
<form:form modelAttribute="echoForm" action="${pageContext.request.contextPath}/echo/hello">
<form:label path="name">Input Your Name:</form:label>
<form:input path="name" />
<input type="submit" />
</form:form>
</body>
</html>
(1) HTML form is constructed using tag library. Specify the name of form object created by Controller in
modelAttribute attribute.
Refer here for tag library.
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
68 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<p>
Hello <c:out value="${name}" /> <%-- (2) --%>
</p>
</body>
</html>
(2) Output “name” passed from Controller. Take countermeasures for XSS using c:out tag.
Note: Here, XSS countermeasure taken using c:out standard tag, however, f:h() function provided in com-
mon library can be used easily. Refer to XSS Countermeasures for details.
Till this point Input validation is not implemented in this application. In Spring MVC, Bean Validation and
annotation based input validation can be easily implemented. For example name input validation is performed in
Eco Application.
package com.example.helloworld.app.echo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull // (1)
@Size(min = 1, max = 5) // (2)
private String name;
(1) Whether name parameter exists in HTTP request or not can be checked by @NotNull annotation.
(2) Whether the size of name is more than or equal to 1 and less than or equal to 5 can be checked by
@Size(min = 1, max = 5).
Implement the input check and the error handling when an error occurs in the input check.
70 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
package com.example.helloworld.app.echo;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("echo")
public class EchoController {
@ModelAttribute
public EchoForm setUpEchoForm() {
EchoForm form = new EchoForm();
return form;
}
@RequestMapping
public String index(Model model) {
return "echo/index";
}
(1) In Controller, add @Validated annotation to the argument on which validation needs to be
executed. Also add BindingResult object to the arguments.
Input validation is automatically performed using Bean Validation and the result is stored in
BindingResult object.
(2) Error can be checked by executing hasErrors method. If there is an input error, it returns View
name to display the input screen.
Add the implementation of displaying input error message on input screen (src/main/webapp/WEB-
INF/views/echo/index.jsp).
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form:form modelAttribute="echoForm" action="${pageContext.request.contextPath}/echo/hello">
<form:label path="name">Input Your Name:</form:label>
<form:input path="name" />
<form:errors path="name" cssStyle="color:red" /><%-- (1) --%>
<input type="submit" />
</form:form>
</body>
</html>
(1) Add form:errors tag for displaying error message when an error occurs on input screen.
<!DOCTYPE html>
<html>
<head>
<title>Echo Application</title>
</head>
<body>
<form id="echoForm" action="/helloworld/echo/hello" method="post">
<label for="name">Input Your Name:</label>
<input id="name" name="name" type="text" value=""/>
72 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Summary
If above points are still not understood, it is recommended to read this chapter again and start again from building
the environment.
74 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• Application layer
• Domain layer
• Infrastructure layer
Both application layer and infrastructure layer depend on domain layer, however domain layer should not depend
on other layers. There can be changes in the application layer with the changes in domain layer, However, there
should be no changes in domain layer with the changes in application layer.
Note: Application layer, domain layer, infrastructure layer are the terms explained in “Domain-Driven Design
(2004, Addison-Wesley)” of Eric Evans. However, though the terms are used, they are not necessarily as per the
concept used in Domain Driven Design.
Flow of data from input to output is Application layer → Domain layer → Infrastructure layer, hence explanation
is given in this sequence.
Application layer
• Calls components in the domain layer corresponding to the request from clients.
This layer should be as thin as possible and should not contain any business rules.
Controller
• Controls the screen flow (mapping the request and returning the View corresponding to the result)
• Calls services in the domain layer (executing main logic corresponding to the request)
In Spring MVC, POJO class having @Controller annotation becomes the Controller class.
Note: When storing the client input data in the session, controllers also have a role to control the life cycle of
data to be stored in the session.
View
View’s responsibilities is generating output (including the UI provision) to the client. Returns output results in
various formats such as HTML/PDF/Excel/JSON.
Tip: If use the JSON or XML format output results for the REST API or Ajax request,
HttpMessageConverter class plays View’s responsibility.
76 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Form
• Represents the form of HTML. (sending form information to the Controller, or outputting the processing
result data to form of HTML)
In Spring MVC, form object are the POJO class that store request parameters. It is called as form backing bean.
Note: To retain the domain layer independent from the application layer, need to perform following processing
at the application layer.
If conversion code is too many, recommend to perform of either or both of the following measures and keep
Controller’s source code in simple state.
Tip: If use the JSON or XML as input data for REST API or Ajax request, Resource class plays Form’s
responsibility. Also, HttpMessageConverter class plays responsibility to convert JSON or XML input data
into Resource class.
Helper
Note: The main duty of Controller is routing (URL mapping and sepcifying the destination). If there is any
processing required (converting JavaBean etc), then that part must be cut-off from controller and must be delegated
to helper classes.
The existance of Helper class improves the visibility of Controller; hence, it is OK to think as a part of Controller.
(Similar to private methods of Controller)
Domain layer
Domain layer is the core of the application, and execute business rules.
• DomainObject
• Checking business rules corresponding to the DomainObject (like whether there is sufficient balance when
crediting amount into the account)
• Executing business rules for the DomainObject (reflects values corresponding to business rules)
DomainObject
DomainObject is a model that represents resources required for business and items generated in the business
process.
• Resource models such as Employee, Customer, Product etc. (generally indicated by a noun)
Domain Object is the Entity that represents an object which indicates 1 record of database table.
Note: Mainly Models holding state only are handled in this guideline.
In “Patterns of Enterprise Application Architecture (2002, Addison-Wesley)” of Martin Fowler, Domain Model is
defined as Item having state and behavior. We will not be touching such models in detail.
78 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
In this guideline, Rich domain model proposed by Eric Evans is also not included. However, it is mentioned here
for classification.
Repository
It can be thought of as a collection of DomainObjects and is responsible for CRUD operations such as create, read,
update and delete of DomainObject.
It is implemented by RepositoryImpl of infrastructure layer. Hence, in this layer, there is no information about
how data access is implemented.
Service
In this guideline, it is recommended to draw the transaction boundary at the method of Service.
Note: Information related to Web such as Form and HttpRequest should not be handled in service.
This information should be converted into domain object in Application layer before calling the Service.
Infrastructure layer
It is responsible for storing the data permanently (location where data is stored such as RDBMS, NoSQL etc.) as
well as transmission of messages.
RepositoryImpl
Represents implementation of Repository interface of domain layer. It covers life cycle management of Domain-
Object.
With the help of this structure, it is possible to hide implementation details from domain layer.
This layer also can be the transaction boundary depending on the requirements.
Tip: When using MyBatis3 or Spring Data JPA, most RepositoryImpls are created automatically.
O/R Mapper
Note: It is more correct to classify MyBatis and Spring JDBC as “SQL Mapper” and not “O/R Mapper”; however,
in this guideline it is treated as “O/R Mapper”.
It integrates a data store other than the database( such as messaging system, Key-Value-Store, Web service, existing
legacy system, external system etc.).
80 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
As explained earlier, domain layer is the core of the application, and application layer and infrastructure layer are
dependent on it.
Even if there is change in implementation technology, the differences can be absorbed in respective layers and
there should not be any impact on domain layer. Coupling between layers is done by using interfaces and hence
they can be made independent of implementation technology being used in each layer.
82 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
No. Description
Controller receives the Request.
1.
(Optional) Controller calls Helper and converts the Form information to DomainObject or DTO.
2.
Service returns DomainObject or DTO which is the result of business logic execution to Controller.
7.
(Optional) Controller calls the Helper and converts DomainObject or DTO to Form.
8.
Please refer to the below table to determine whether it is OK to call a component from another component.
Controller
Service
Repository
If services that can be used even from other services are required, SharedService should be created in order to
clarify such a possibility. Refer to Domain Layer Implementation for the details.
Note: It may be difficult to follow the above rules at the initial phase of application development. If looking at
a very small application, it can be created quickly by directly calling the Repository from Controller. However,
when rules are not followed, there will be many maintainability issues when development scope becoms larger.
It may be difficult to understand the impact of modifications and to add common logic which is cross-cutting in
nature. It is strongly recommended to pay attention to dependency from the beginning of development so that
there will be no problem later on.
Hiding the implementation details and sharing of data access logic are the merits of creating a Repository.
However, it is difficult to share data access logic depending on the project team structure (multiple companies may
separately implement business processes and control on sharing may be difficult etc.) In such cases, if abstraction
of data access is not required, O/R Mapper can be called directly from Service as shown in the following diagram,
without creating the repository.
84 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Controller
Service
Here, recommended project structure is explained when application layering is done as described earlier.
Basically, it is recommended to create the multiple projects with the following configuration.
Note: Classes of infrastructure layer such as RepositoryImpl are also included in project-domain. Originally,
[projectName]-infra project should be created separately; however, normally there is no need to conceal the im-
plementation details in the infra project and development becomes easy if implementation is also stored in domain
project. Moreover, when required, [projectName]-infra project can be created.
Tip: For the example of multi-project structure, refer to Sample Application or Test Application of Common
Library.
[projectName]-domain
[projectName]-domain
└ src
└ main
├ java
| └ com
| └ example
| └ domain ...(1)
| ├ model ...(2)
| | ├ Xxx.java
| | ├ Yyy.java
| | └ Zzz.java
| ├ repository ...(3)
| | ├ xxx
| | | └ XxxRepository.java
| | ├ yyy
| | | └ YyyRepository.java
| | └ zzz
| | ├ ZzzRepository.java
| | └ ZzzRepositoryImpl.java
| └ service ...(4)
| ├ aaa
| | ├ AaaService.java
| | └ AaaServiceImpl.java
| └ bbb
| ├ BbbService.java
| └ BbbServiceImpl.java
└ resources
└ META-INF
└ spring
├ [projectName]-codelist.xml ...(5)
├ [projectName]-domain.xml ...(6)
└ [projectName]-infra.xml ...(7)
86 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
No. Details
Package to store configuration elements of domain layer.
(1)
(2)
(5)
(6)
(7)
[projectName]-web
[projectName]-web
└ src
└ main
├ java
| └ com
| └ example
| └ app ...(1)
| ├ abc
| | ├ AbcController.java
| | ├ AbcForm.java
| | └ AbcHelper.java
| └ def
| ├ DefController.java
| ├ DefForm.java
| └ DefOutput.java
├ resources
| ├ META-INF
| | └ spring
| | ├ applicationContext.xml ...(2)
| | ├ application.properties ...(3)
| | ├ spring-mvc.xml ...(4)
| | └ spring-security.xml ...(5)
| └ i18n
| └ application-messages.properties ...(6)
└ webapp
├ resources ...(7)
└ WEB-INF
├ views ...(8)
| ├ abc
| | ├ list.jsp
| | └ createForm.jsp
| └ def
| ├ list.jsp
| └ createForm.jsp
└ web.xml ...(9)
88 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
No, Details
Package to store configuration elements of application layer.
(1)
(2)
(3)
(4)
(5)
Define the messages and other contents to be used for screen display (internationalization).
(6)
(7)
(8)
(9)
[projectName]-env
[projectName]-env
├ configs ...(1)
| └ [envName] ...(2)
| └ resources ...(3)
└ src
└ main
└ resources ...(4)
├ META-INF
| └ spring
| ├ [projectName]-env.xml ...(5)
| └ [projectName]-infra.properties ...(6)
├ dozer.properties
├ log4jdbc.properties
└ logback.xml ...(7)
No. Details
Directory to define configurations depends on the environment for all environments.
(1)
(4)
Bean definitions that depend on the local development environment (like dataSource etc).
(5)
(6)
(7)
Note: The purpose of separating [projectName]-domain and [projectName]-web into different projects is to
prevent reverse dependency among them.
It is natural that [projectName]-web uses [projectName]-domain; however, [projectName]-domain must not refer
projectname]-web.
90 2 Architecture Overview
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
If configruation elements of both, [projectName]-web and [projectName]-domain, are kept in a single project, it
may lead to an incorrect reference by mistake. It is strongly recommended to prevent reference to [projectName]-
web from [projectName]-domain by separating the project and setting order of reference.
Note: The reason for creating [projectName]-env is to separate the information depending on the environment
and thereby enable switching of environment.
For example, set local development environment by default and at the time of building the application, create war
file without including [projectName]-env. By creating a separate jar for integration test environment or system test
environment, deployment becomes possible just by replacing the jar of corresponding environment.
Further, it is also possible to minimize the changes in case of project where RDBMS being used changes.
If the above point is not considered, contents of configuration files have to changed according to the target envi-
ronment and the project has to be re-build.
Application Development
Description of various rules as well as recommended implementation on the use of TERASOLUNA Server Frame-
work for Java (5.x).
In this guideline, it is recommended to adopt multi-project configuration. For description of the recommended
multi-project configuration, refer [Project structure].
Type Usage
This should be used when developing a real application that is to be released in commercial
environment.
Blank project of
Following types of project templates are provided as Archetype of Maven.
multi-project structure
• Template that includes settings for MyBatis3
• Template that includes settings for JPA (Spring Data JPA)
This guideline recommends using a project having multi-project structure.
This should be used when creating simple applications such as POC (Proof Of Concept),
prototype, sample, etc.
Blank project of
Following types of project templates are provided as Archetype of Maven. (Projects for
single-project
Eclipse WTP are also provided; however, their description is omitted in this chapter)
structure
• Template that includes settings for MyBatis3
• Template that includes settings for JPA (Spring Data JPA)
• Template that does not depend on O/R Mapper
This guideline follows a procedure wherein various tutorials are performed using a single
project.
The multi-project structured development project will be created using the archetype:generate of the Maven
Archetype Plugin .
Note: Prerequisite
are prerequisites.
If prerequisite conditions are not satisfied, it is necessary to perform these setups first.
94 3 Application Development
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
cd C:\work
Parameter Description
batch mode (skips interaction)
-B
-DarchetypeCatalog
-DarchetypeGroupId
Specify archetypeId (ID to identify the template) of the blank project. (Customiza-
tion required)
-DarchetypeArtifactId
specify one of the following archetypeId.
• terasoluna-gfw-multi-web-blank-mybatis3-archetype
• terasoluna-gfw-multi-web-blank-jpa-archetype
In above example, terasoluna-gfw-multi-web-blank-mybatis3-archetype
is specified.
Specify version of the blank project.(Fixed)
-DarchetypeVersion
Specify groupId of the project that you want to create. (Customization required)
In above example, "com.example.todo" is specified.
-DgroupId
Specify artifactId of the project that you want to create. (Customization required)
In above example, "todo" is specified.
-DartifactId
Specify version of the project that you want to create. (Customization required)
In above example, "1.0.0-SNAPSHOT" is specified.
-Dversion
If the project creation successes, following type of log will be printed. (The following output is an example when
project is created using the MyBatis3 Archetype)
(... omit)
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: terasoluna-gfw-multi-web-bl
96 3 Application Development
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.example.todo
[INFO] Parameter: artifactId, Value: todo
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: package, Value: com.example.todo
[INFO] Parameter: packageInPathFormat, Value: com/example/todo
[INFO] Parameter: package, Value: com.example.todo
[INFO] Parameter: version, Value: 1.0.0-SNAPSHOT
[INFO] Parameter: groupId, Value: com.example.todo
[INFO] Parameter: artifactId, Value: todo
[INFO] Parent element not overwritten in C:\work\todo\todo-env\pom.xml
[INFO] Parent element not overwritten in C:\work\todo\todo-domain\pom.xml
[INFO] Parent element not overwritten in C:\work\todo\todo-web\pom.xml
[INFO] Parent element not overwritten in C:\work\todo\todo-initdb\pom.xml
[INFO] Parent element not overwritten in C:\work\todo\todo-selenium\pom.xml
[INFO] project created from Archetype in dir: C:\work\todo
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.929 s
[INFO] Finished at: 2015-07-31T12:03:21+00:00
[INFO] Final Memory: 10M/26M
[INFO] ------------------------------------------------------------------------
If the project creation successes, Maven multi-project gets created. For detail description of the project that you
have created in the Maven Archetype, Refer [Structure of the development project].
todo
├-- pom.xml
├-- todo-domain
├-- todo-env
├-- todo-initdb
├-- todo-selenium
└-- todo-web
Depending upon the application, there are several locations where customization is required in the Maven
Archetype created project.
• Message wording
• Error screen
• DataSource configuration
• Settings of Authentication・Authorization
• Definition of Logging
For these customizations, Refer to “How to use” of each section and customize if required.
Note: Part that is expressed as artifactId in the following description needs to be read by replacing the
artifactId which is specified at the time of creating a project.
98 3 Application Development
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
such information set in Archetype projects. The actual settings contents indicated below.
<name>TERASOLUNA Server Framework for Java (5.x) Web Blank Multi Project</name>
<description>Web Blank Multi Project using TERASOLUNA Server Framework for Java (5.x)</description
<url>http://terasoluna.org</url>
<inceptionYear>2014</inceptionYear>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>manual</distribution>
</license>
</licenses>
<organization>
<name>TERASOLUNA Framework Team</name>
<url>http://terasoluna.org</url>
</organization>
In the Maven Archetype created project, the x.xx.fw.9999 format message ID used at the time of,
[application-messages.properties]
[JSP]
<div class="error">
<c:if test="${!empty exceptionCode}">[${f:h(exceptionCode)}]</c:if>
<spring:message code="e.xx.fw.5001" />
</div>
[applicationContext.xml]
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<!-- ... -->
<entry key="ResourceNotFoundException" value="e.xx.fw.5001" />
<!-- ... -->
</bean>
The x.xx.fw.9999 format message ID is a message ID system that is introduced in [Message Management] of
this guideline but, the value of the project division is in the state of provisional value [xx].
Note:
• If the message ID system introduced in this guideline is used, specify the appropriate values to the
project classification. For the message ID system introduced in this guideline, Refer [Result messages].
• If the message ID system introduced in this guideline is not used, it is necessary to replace all the message
IDs those are used in the customization targeted file indicated below.
Message wording
In the Maven Archetype created project, number of message definitions are provided but, message wordings are
simple messages. Actual messages (sampling) are indicated below.
[application-messages.properties]
# ...
# typemismatch
typeMismatch="{0}" is invalid.
# ...
Note: Modify the message wording depending upon the application requirements (such as message terms)
Error screen
In the Maven Archetype created project, JSP and HTML are provided for displaying an error screen for every kind
of errors but,
• screen layout
• screen title
etc are simple implementation. Actual JSP implementation (sampling) is indicated below.
[JSP]
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Resource Not Found Error!</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Resource Not Found Error!</h1>
<div class="error">
<c:if test="${!empty exceptionCode}">[${f:h(exceptionCode)}]</c:if>
<spring:message code="e.xx.fw.5001" />
</div>
<t:messagesPanel />
<br>
<!-- ... -->
<br>
</div>
</body>
</html>
Note: Modify the JSP and HTML depending upon the application requirements (such as UI terms) used
for displaying an error screen.
In the Maven Archetype created project, screen layouts are configured using Tiles but, the copyright of the screen
footer portion is in a state of provisional value [Copyright © 20XX CompanyName]. Actual JSP
implementation (sampling) is indicated below.
[template.jsp]
<div class="container">
<tiles:insertAttribute name="header" />
<tiles:insertAttribute name="body" />
<hr>
<p style="text-align: center; background: #e5eCf9;">Copyright
© 20XX CompanyName</p>
</div>
Note: If screen layouts are configured using Tiles, specify appropriate value to the copyright.
In the Maven Archetype created project, in-memory database (H2 Database) setting is configured but, these set-
tings are done for the small operation (Prototyping and POC (Proof Of Concept)) verification. Therefore, these
could be unnecessary settings while having regular application development.
[artifactId-env.xml]
<jdbc:initialize-database data-source="dataSource"
ignore-failures="ALL">
<jdbc:script location="classpath:/database/${database}-schema.sql" />
<jdbc:script location="classpath:/database/${database}-dataload.sql" />
</jdbc:initialize-database>
└-- src
└-- main
└-- resources
├-- META-INF
(...)
├-- database
| ├-- H2-dataload.sql
| └-- H2-schema.sql
Note: While having regular application development, remove the directory which is maintained for defini-
tion and SQL files for setting up a In-memory database (H2 Database)
DataSource configuration
In the Maven Archetype created project, DataSource setting is done for accessing in-memory database (H2
Database) but, these settings are done for the small operation (Prototyping and POC (Proof Of Concept)) veri-
fication. Therefore it is necessary to change the DataSource settings for accessing the actual running database
application while having regular application development.
[artifactId/artifactId-domain/pom.xml]
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
[artifactId-infra.properties]
database=H2
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
[artifactId-env.xml]
Note: Change the DataSource settings for accessing the actual running database application while having
regular application development.
In the Maven Archetype created project, the use of Apache Commons DBCP is configured but, there are many
cases that adopting a method of accessing a DataSource via JNDI (Java Naming and Directory Interface) by use
of DataSource provided by the application server.
Again there are some cases where Apache Commons DBCP is used on development environment and DataSource
provided by the application server is used on test as well as production environment.
For how to set-up the DataSource, Refer [Datasource settings of Database Access (Common)].
Bean definition file for defining environment de- If DataSource provided by the application server
3.
pendent components is used, change the configuration to use the Data-
Source that is obtained via JNDI.
artifactId/artifactId-env/src/main/resources/META-INF/spring/artifactId-env.x
For how to set-up the DataSource, Refer [Data-
source settings of Database Access (Common)].
Note: About the database property of the property file for defining environment dependent setting
The database property is unnecessary property if MyBatis is used as O/R Mapper. You may remove this but
you may leave the settings in order to specify the database being used.
It is fine to remove the comment out of POM file in case of PostgreSQL or Oracle database is used.. Modify the
JDBC driver version by actual use of the corresponding database version.
However, if Oracle is used, it is necessary to install the Oracle JDBC driver in the local repository of Maven before
removing the comment.
• artifactId/pom.xml
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<!-- <dependency> -->
<!-- <groupId>com.oracle</groupId> -->
<!-- <artifactId>ojdbc7</artifactId> -->
<!-- <version>${ojdbc.version}</version> -->
<!-- </dependency> -->
<postgresql.version>9.3-1102-jdbc41</postgresql.version>
<ojdbc.version>12.1.0.2</ojdbc.version>
• artifactId/artifactId-domain/pom.xml
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>provided</scope> -->
</dependency> -->
<!-- <dependency> -->
<!-- <groupId>com.oracle</groupId> -->
<!-- <artifactId>ojdbc7</artifactId> -->
<!-- <scope>provided</scope> -->
<!-- </dependency> -->
• Project structure that takes into account the exclusion of environmental dependency introduced in this
guideline
In addition, various settings have been included that is recommended in this guideline
and, as a simple component implementation of low (=necessary to develop every kind of application) dependency
on the application requirements,
In the Maven Archetype created project, the recommended settings are done which are required for building a
traditional Web application (application that receives the request parameters and respond the HTML).
Therefore, unnecessary setting exists in building a REST API for handling JSON or XML. If you want to create
a project for building REST API, need to apply the REST API related settings by referring to the [Application
settings of RESTful Web Service].
Note: Part that is expressed as artifactId in the following description needs to be read by replacing the
artifactId which is specified at the time of creating a project.
Multi-project structure
artifactId
├-- pom.xml ... (1)
├-- artifactId-web ... (2)
├-- artifactId-domain ... (3)
├-- artifactId-env ... (4)
├-- artifactId-initdb ... (5)
└-- artifactId-selenium ... (6)
The entire multi-project configuration is defined in POM (Project Object Model) file.
Mainly following definitions are done in this file.
(1)
• Version of the dependent libraries
• Build plug-ins settings (setting of how to build)
Refer [Hierarchical structure of the project] for the hierarchical relationship of multi-project.
Module that manages the application layer (Web layer) components.
Mainly following components and files are managed in this module.
(2)
• Controller class
• Validator class for relational check
• Form class (the Resource class in case of REST API)
• View (JSP)
• CSS file
• JavaScript file
• JUnit for the application layer components
• Bean definition file for defining the application layer components
• Web application configuration file (web.xml)
• Message definition file
The project created in Maven Archetype is the exact multi-module structured project.
This is supplement that the multi-module and multi-project is being used as the same meaning in this guideline.
Note: Development projects required for two Web applications and one common library
• bar-parent
• bar-initdb
• bar-common
• bar-common-web
• bar-domain-a
• bar-domain-b
• bar-web-a
• bar-web-b
• bar-env
• bar-web-a-selenium
• bar-web-b-selenium
• bar-parent
Project called as a parent-pom (parent POM). A simple project consisting of only pom.xml file. It never
contains other source code or configuration files. Common setting information specified in the parent POM
can be reflected in other project by specifying this bar-parent project into <parent> tag.
• bar-initdb
Stores RDBMS table definitions (DDL) and SQL statements for INSERT the initial data. This also managed
as a maven project. By defining sql-maven-plugin in pom.xml, it is possible to automate the execution of
DDL statements and initial data INSERT statements for any RDBMS in the course of the build lifecycle.
• bar-common
Stores common library in the project. Web related classes are placed in the bar-common-web by making it
as a web-independent.
• bar-common-web
• bar-domain-a
Stores unit test cases and domain layer java classes related to “a” domain. Finally *.jar file is created.
• bar-domain-b
• bar-web-a
Stores application layer java classes, jsps, configuration files, unit test cases. Finally created *.war file is
created as the Web application. bar-web-a having dependency on bar-common and bar-env.
• bar-web-b
This is a Web application as one more subsystem. Structure is the same as the bar-web-a.
• bar-env
• bar-web-a-selenium
Project that stores test cases using Selenium WebDriver for web-a project.
• bar-web-b-selenium
Project that stores test cases using Selenium WebDriver for web-b project.
Module that manages the application layer (Web layer) components are explained.
artifactId-web
├-- pom.xml ... (1)
The web module configuration is defined in POM (Project Object Model) file. Following definitions
are done in this file.
(1)
• Definition of dependent libraries and build plug-ins
• Definition to create a war file
Note: About the module name of the web module while creating a project for REST API
The application type can be easily distinguished, if the module name is assigned the name of artifactId-api
while building a REST API.
└-- src
├-- main
| ├-- java
| | └-- com
| | └-- example
| | └-- project
| | └-- app ... (2)
| | └-- welcome
| | └-- HelloController.java ... (3)
| ├-- resources
| | ├-- META-INF
| | | ├-- dozer ... (4)
| | | └-- spring ... (5)
| | | ├-- application.properties ... (6)
| | | ├-- applicationContext.xml ... (7)
| | | ├-- spring-mvc.xml ... (8)
| | | └-- spring-security.xml ... (9)
| | └-- i18n ... (10)
| | └-- application-messages.properties ... (11)
The controller class for receiving a request to display the Welcome page.
(3)
The directory in which a mapping definition file of Dozer (Bean Mapper) is stored, Refer to [Bean
Mapping (Dozer)] for Dozer.
(4)
It is an empty directory at the time of creation. If the mapping file is required (if high mapping is
required), it gets automatically read in case of stored under this directory.
Directory contains the property file and Spring Framework bean definition file.
(5)
Properties file that defines the settings to be used in the application layer.
It is an empty file at the time of creation.
(6)
(10)
Property file that defines the messages to be used in the application layer.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Refer [Relationship of bean definition file and application context structure] for the application context
and bean definition file related.
| └-- webapp
| ├-- WEB-INF
| | ├-- tiles ... (12)
| | | └-- tiles-definitions.xml
| | ├-- views ... (13)
| | | ├-- common
| | | | ├-- error ... (14)
| | | | | ├-- accessDeniedError.jsp
| | | | | ├-- businessError.jsp
| | | | | ├-- dataAccessError.jsp
| | | | | ├-- invalidCsrfTokenError.jsp
| | | | | ├-- missingCsrfTokenError.jsp
| | | | | ├-- resourceNotFoundError.jsp
| | | | | ├-- systemError.jsp
| | | | | ├-- transactionTokenError.jsp
| | | | | └-- unhandledSystemError.html
| | | | └-- include.jsp ... (15)
| | | ├-- layout ... (16)
| | | | ├-- header.jsp
| | | | └-- template.jsp
| | | └-- welcome
| | | └-- home.jsp ... (17)
| | └-- web.xml ... (18)
| └-- resources ... (19)
| └-- app
| └-- css
| └-- styles.css ... (20)
└-- test
├-- java
└-- resources
Directory that contains the Tiles configuration files. Refer [Screen Layout using Tiles] for the Tiles
configuration files.
(12)
(13)
Directory that contains the JSP and HTML for displaying error screens.
At the time of creation, JSPs (HTMLs) are stored corresponding to the errors that may occur during
(14)
application execution.
Note: Error screen JSP and HTML should be modified according to the application require-
ments (Such as UI Terms).
Directory that contains the JSP files for the Tiles layout. Refer [Screen Layout using Tiles] for JSP
files for the Tiles layout.
(16)
(17)
(18)
(20)
artifactId-domain
├-- pom.xml ... (1)
The domain module configuration is defined in POM (Project Object Model) file. Following definitions
are done in this file.
(1)
• Definition of dependent libraries and build plug-ins
• Definition to create a jar file
└-- src
├-- main
| ├-- java
| | └-- com
| | └-- example
| | └-- project
| | └-- domain ... (2)
| | ├-- model
| | ├-- repository
| | └-- service
| └-- resources
| └-- META-INF
| ├-- dozer ... (3)
| └-- spring ... (4)
| ├-- artifactId-codelist.xml ... (5)
| ├-- artifactId-domain.xml ... (6)
| └-- artifactId-infra.xml ... (7)
(2)
The directory in which a mapping definition file of Dozer (Bean Mapper) is stored, Refer to [Bean
Mapping (Dozer)] for Dozer.
(3)
It is an empty directory at the time of creation. If the mapping file is required (if high mapping is
required), it gets automatically read in case of stored under this directory.
Directory contains the property file and Spring Framework bean definition file.
(4)
(5)
└-- test
├-- java
| └-- com
| └-- example
| └-- project
| └-- domain
| ├-- repository
| └-- service
└-- resources
└-- test-context.xml ... (8)
Bean definition file for defining the domain layer unit test components.
(8)
└-- src
├-- main
| ├-- java
(...)
| └-- resources
| ├-- META-INF
| | ├-- dozer
| | ├-- mybatis ... (9)
| | | └-- mybatis-config.xml ... (10)
| | └-- spring
(...)
| └-- com
| └-- example
| └-- project
| └-- domain
| └-- repository ... (11)
| └-- sample
| └-- SampleRepository.xml ... (12)
(9)
(11)
Module that manages the environment dependent configuration files are explained.
artifactId-env
├-- configs ... (1)
| ├-- production-server ... (2)
| | └-- resources
| └-- test-server
| └-- resources
├-- pom.xml ... (3)
The env module configuration is defined in POM (Project Object Model) file. Following definitions
are done in this file.
(3)
• Definition of dependent libraries and build plug-ins
• Definition of Profile to create a jar file for each environment
└-- src
└-- main
└-- resources ... (4)
├-- META-INF
| └-- spring
| ├-- artifactId-env.xml ... (5)
| └-- artifactId-infra.properties ... (6)
├-- database ... (7)
| ├-- H2-dataload.sql
| └-- H2-schema.sql
├-- dozer.properties ... (8)
├-- log4jdbc.properties ... (9)
└-- logback.xml ... (10)
(4)
Directory that contains the SQL to set up an in-memory database (H2 Database).
This directory is prepared while performing small operation verification . Basically remove this di-
(7)
rectory because this directory is not intended to use in the actual application development.
Property file for carrying out the Dozer (Bean Mapper) global settings. For Dozer refer [Bean Mapping
(Dozer)].
(8)
It is an empty file at the time of creation. (The warning log appears at the start-up time if file is not
exist, the empty file is prepared in order to prevent it)
Property file for carrying out the Log4jdbc-remix (library to perform the JDBC-related log output)
global settings. For Log4jdbc-remix, refer [JDBC debug log settings].
(9)
At the time of creation, new line character related setting are specified for those SQLs which are going
to be printed in log.
Configuration file of the Logback (log output). For the log output refer [Logging].
(10)
Module that manages the SQL file to initialize the database is explained.
artifactId-initdb
├-- pom.xml ... (1)
└-- src
└-- main
└-- sqls ... (2)
The initdb module configuration is defined in POM (Project Object Model) file. Following definitions
are done in this file.
(1)
• Definition of build plug-ins (SQL Maven Plugin)
Simple configuration for PostgreSQL is defined at the time of creation.
Directory for storing the database initialization SQL files.
It is an empty directory at the time of creation. For how to create, Refer Sample application of initdb
(2)
project.
mvn sql:execute
Module that manages the E2E (End To End) testing components used in Selenium explained.
artifactId-selenium
├-- pom.xml ... (1)
└-- src
└-- test ... (2)
├-- java
| └-- com
| └-- example
| └-- project
| └-- selenium
| └-- welcome
| └-- HelloTest.java ... (3)
└-- resources
└-- META-INF
└-- spring
├-- selenium.properties ... (4)
└-- seleniumContext.xml ... (5)
The selenium module configuration is defined in POM (Project Object Model) file.
Following definitions are done in this file.
(1)
• Definition of dependent libraries and build plug-ins
• Definition to create a war file
3.1.5 Appendix
The hierarchical structure of the project indicated below which is created in Maven Archetype.
Tip: The configuration has been changed like <dependencyManagement> of Spring IO Platform is imported
from version 5.0.0.RELEASE, we have adopted a style that version management of recommended libraries are
done in Spring IO Platform.
Warning: Since the configuration has been changed like <dependencyManagement> of Spring IO Plat-
form is imported from version 5.0.0.RELEASE, You are no longer able to access the version management
properties from the child project.
Therefore, if property values are referring or overwriting at the child project, pom file should be modified while
upgrading from version 1.0.x.
Furthermore, it is possible to access the conventional version management properties for recommended li-
braries (TERASOLUNA Server Framework for Java (5.x) recommended library) which are not managed by
the Spring IO Platform.
Relationship of bean definition file and structure of the Spring Framework application context (DI container)
indicated below.
Note: About the operation when registered the same components in both application contexts.
If same components are registered in both application context for web application and application context for
DispatcherServlet, injected component will be the registered component in the same application con-
text(Application context for DispatcherServlet) and this point is supplemented here.
In particular, it is necessary to be careful that do not register the domain layer component (such as Service and
Repository) to application context for the DispatcherServlet.
If domain layer components are registered to the application context for the DispatcherServlet, trouble like
the database operations are not committed occurs due to component that performs the transaction control (AOP)
is not enabled.
Furthermore, the settings are done in the project created using Maven Archetype so that the above events don’t
occur. It is necessary to be careful while performing modification or addition of the settings.
Todo
In order to increase the understanding of various settings, planning to add explanation of a configuration file.
• If functional description is explained somewhere, Reference to the functional description will be noted
down.
In “Create development project”, a method to create a development project of multi-project configuration by using
archetype:generate of Maven Archetype Plugin is described. Although Maven is used for the operations in the
online environment, a method is described below for how to use it in offline environment as well.
To continue project development in the offline environment, the files like libraries and plugins necessary for
development must be copied in advance. The operation below should be performed in online environment.
Move to root directory of development project. Here, the project created using “Create development project” is
used for the explanation.
cd C:\work\todo
Copy the files like libraries and plugins necessary for project development. Files are copied by executing
dependency:go-offline of Maven Archetype Plugin.
Parameter Description
Specify copy destination. A new destination is created if a copy destination does not
exist. At present, copy destination is specified as a repository.
–Dmaven.repo.local
Create a war file or a jar file in order to facilitate the distribution of deliverables. At that time, files like libraries
and plugins necessary for build are copied.
(... omit)
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO]
[INFO] TERASOLUNA Server Framework for Java (5.x) Web Blank Multi Project (MyBa
tis3) SUCCESS [ 0.006 s]
[INFO] todo-env ........................................... SUCCESS [ 46.565 s]
[INFO] todo-domain ........................................ SUCCESS [ 0.684 s]
[INFO] todo-web ........................................... SUCCESS [ 12.832 s]
[INFO] todo-initdb ........................................ SUCCESS [ 0.067 s]
[INFO] todo-selenium ...................................... SUCCESS [01:13 min]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:14 min
[INFO] Finished at: 2015-10-01T10:32:34+09:00
[INFO] Final Memory: 36M/206M
[INFO] ------------------------------------------------------------------------
Above, files like libraries and plugins necessary for project development are copied. Operation is completed
when the repository is copied to ${HOME}/.m2 of offline environment machine. If a process which has not been
executed even once in online environment is executed in offline environment, necessary files like libraries and
plugins cannot be fetched resulting in the process failure. However, by copying the files, the development can be
continued uninterrupted even after moving to offline environment.
This guideline recommends the structure of creating Entity classes and Repository for the following reasons.
1. By splitting the overall logic into business logic (Service) and the logic to access business data, the
implementation scope of business logic gets limited to the implementation of business rules,
2. Access logic to business data is standardized by consolidating the operations of business data in the
Repository.
Note: Though this guideline recommends a structure to create Entity classes and Repository, it is not
mandatory to perform development in this structure.
Decide a structure by taking into account the characteristics of the application as well as the project (struc-
ture of development team and development methodology).
A case where application is created by multiple development teams is assumed; however, the flow itself remains
same even if developed by a single team.
(1) Common Common development team designs and creates Entity classes.
development team
(2) Common Common development team works out access pattern for the Entity
development team classes extracted in (1) and designs methods of Repository interface.
Common development team should implement the methods to be shared
by multiple development teams.
(3) Common Common development team provides Entity classes and Repository
development team created in (1) and (2) to the business application development team.
At this time, it requests each business application development team to
implement the Repository interface.
(4) Business application Business application development team takes charge of the
development team implementation of Repository interface.
(5) Business application Business application development team develops Service interface and
development team Service class using the Entity class and Repository provided by
the common development team and the Repository implementation class
created by the team itself.
Warning: A system having a large development scope is often developed by assigning the application
to multiple teams. In that case, it is strongly recommended to provide a common team to design Entity
classes and Repository.
When there is no common team, O/R Mapper(MyBatis, etc.) should be called directly from Service and
a method to access business data should be adopted without creating Entity classes and Repository .
1. Create Entity class for each table. However, Entity class is not required for mapping tables
which represent the relationship between the tables.
Further, when the tables are not normalized, Entity class
for each table rule may not be applicable. Refer to the
Warning as well as Note outside this table
for the approach related to not-normalized tables.
2. When there is a FK (Foreign Key) in When there is 1:N relationship with FK destination table,
the table, the Entity class of FK use either java.util.List<E> or
destination table must be defined as java.util.Set<E>.
one of the properties of this Entity. The Entity corresponding to the FK destination table is
called as the related Entity in this guideline.
3. Treat the code related tables as Code related tables are to manage the pairs of code value
java.lang.String rather than and name.
as an Entity. When there is a need to bifurcate the process as per code
values, enum class corresponding to code value should be
created and it must be defined as property.
Warning: When table is not normalized, check whether to use the method of creating the Entity
classes and Repository by considering the following points. Since the unnormalized tables do not have
good compatibility with JPA, it is better not to use JPA.
• Creating an appropriate Entity class may often not be possible because of increased difficulty in
creating entities if the tables are not normalized.
In addition, efforts to create an Entity classes also increases.
Two viewpoints must be taken into consideration here. Firstly “Can we assign an engineer who
can perform normalization properly?” and secondly “Is it worth taking efforts for creating
normalized Entity classes?”.
• If the tables are not normalized, the logic to fill the gap of differences between the Entity class
and structure of table is required in data access.
Here the viewpoint to be considered is, “Is it worth taking efforts to fill the gap of differences
between the Entity class and structure of table ?”.
The method of creating Entity classes and Repository is recommended; however, the characteristics of
the application as well as the project (structure of development team and development methodology)
must also be taken into account.
Note: If you want to operate business data as application, and as normalized Entity even if the tables
are not normalized, it is recommended to use MyBatis as an implementation of RepositoryImpl of the
infrastructure layer.
MyBatis is the O/R Mapper developed to map the SQL with object and not to map the database table record
with object. Therefore, mapping to the object independent of table structure is possible depending on the
implementation of SQL, .
Table structure
(1) Transaction related t_order Table to store orders. 1 record is stored for 1 order.
(3) t_order_coupon Table to store the coupon used in a single order. Record of
each coupon is stored when multiple coupons are used in
1 order. No record is stored when coupon is not used.
3.2. Domain
(5) Layer Implementation m_category Master table to define product category. 137
Entity structure
If Entity classes are created with the help of policy defined by the above table, it results into the following structure.
(6) ItemCategory Entity class is not created since m_item_category table is the
mapping table to store the relationship between m_item table and
m_category table.
(8) OrderStatus Entity class is not created since c_order_status table is code table.
As it can be observed from the above entity diagram, it might first seem that Order class is the only main entity
class in the shopping site application; however, there are other main entity class as well other than Order class.
Below is the classification of main Entity classes as well as Entity class which are not main.
The following 4 Entities are treated as the main Entity for creating shopping site application.
Sr. No. Entity class Reasons for treating as the main Entity.
(1) Order class It is one of the most important Entity class in the shopping site.
Order class is the Entity indicating the order itself and a
shopping site cannot be created without the Order class.
(2) Item class It is one of the most important Entity class in the shopping site.
Item class is the Entity indicating the products handled in the
shopping site and a shopping site cannot be created without
Item class.
(3) Category class Product categories are displayed usually on the top page or as a
common menu in shopping sites. In such shopping sites,
Category becomes a main entity. Usually operations like
‘search category list’ can be expected.
(4) Coupon class Often discounts through coupons are offered in the shopping
sites as a measure of promoting sales of the products.
In such shopping sites, Coupon becomes a main entity. Usually
operations like ‘search coupon list’ can be expected.
The following are not main Entities for creating shopping site application.
Sr. No. Entity class Reason of not treating Entity as main Entity
(5) OrderItem class This class indicates 1 product purchased in 1 order and exists
only as the related Entity of Order class.
So OrderItem class should not be considered as main Entity.
(6) OrderCoupon This class indicates 1 coupon used in 1 order and exists only as
the related Entity of Order class.
So, OrderCoupon class should not be considered as main Entity.
Roles of Repository
1. To provide to Service, the operations necessary to control Entity lifecycle (Repository interface).
The operations for controlling Entity lifecycle are CRUD operations.
Structure of Repository
Repository consists of Repository interface and RepositoryImpl and performs the following roles.
(1) Repository interface Defines methods to control Defines methods for CRUD operations of
Entity lifecycle required for the Entity and is not dependent on
implementing business logic persistence layer.
(Service). Repository interface belongs to the
domain layer since it plays the roles of
defining the operations on Entity required
for implementing business logic (Service).
Note: Is it possible to hide 100% of persistence platform dependent logic from the Service class ?
In some cases it cannot be hidden completely due to constraints of persistence platform and the libraries
used to access the platform. As much as possible, platform dependent logic should be implemented in
RepositoryImpl instead of Service class. When it is difficult to exclude the platform dependent logic and
merits of doing so are less, persistence platform dependent logic can be implemented as a part of business
logic (Service) process.
A specific example of this is given here. There are cases when unique
constraints violation error is needed to be handled when save method of
org.springframework.data.jpa.repository.JpaRepository interface provided by
Spring Data JPA is called. In case of JPA, there is a mechanism of cache entity operations and SQL is
executed when transactions are committed. Therefore, since SQL is not executed even if save method of
JpaRepository is called, unique constraints violation error cannot be handled in logic. There is a method
(flush method) to reflect cached operations as means to explicitly issue SQLs in JPA. saveAndFlush
and flush methods are also provided in JpaRepository for the same purpose. Therefore, when unique
constraints violation error needs to be handled using JpaRepository of Spring Data JPA, JPA dependent
method (saveAndFlush or flush) must be called.
Warning: The most important purpose of creating Repository is not to exclude the persistence platform
dependent logic from business logic. The most important purpose is to limit the implementation scope
of business logic (Service) to the implementation of business rules. This is done by separating the
operations to access business data in Repository. As an outcome of this, persistence platform dependent
logic gets implemented in Repository instead of business logic (Service).
Creation of Repository
1. Create Repository for the main This means separate Repository for operations of related
Entity only. Entity is not required.
However, there are case when it is better to provide
Repository for the related Entity in specific applications
(for example,
application having high performance requirements etc).
2. Place Repository interface and Repository interface belongs to domain layer and
RepositoryImpl in the same package RepositoryImpl belongs to infrastructure layer. However,
of domain layer. Java package of RepositoryImpl can be same as the
Repository interface of domain layer.
3. Place DTO used in Repository in the For example, DTO to store search criteria or summary
same package as Repository DTO for that defines only a few items of Entity.
interface.
as follows.
Structure of Repository
Entity class used in the explanation of Example of creating Entity class is used as an example, the resulting
configuration is as follows:
• SimpleCrudRepository.java
(3) Method to retrieve the list of all Entities. In Spring Data, it was java.util.Iterable.
Here as a sample, it is set to java.util.List.
(4) Method to fetch collection of Entity objects corresponding to the specified pagination
information (start position, record count, sort information).
Pageable and Page are the interfaces provided by Spring Data.
• TodoRepository.java
An example of creating Repository of Todo Entity, which was created in tutorial, on the basis of
SimpleCrudRepository interface created above is shown below.
// (1)
public interface TodoRepository extends SimpleCrudRepository<Todo, String> {
// (2)
long countByFinished(boolean finished);
}
(1) TodoRepository interface is created by specifying Todo entity in the generic type parameter “T”
and
String class in the generic type parameter “ID”.
(2) Methods not provided by SimpleCrudRepository interface are added in this interface.
In this case, “Method for acquiring count of Todo entity objects for which specified tasks have
been finished” is added.
Count related
4. 1. Method name beginning with countBy to indicate that this method
method
fetches count of Entities which matches with the condition.
2. Return value must be long type.
3. In the method name after “countBy”, physical or logical name of the
field used as search condition must be specified. Hence, the method
150 name must be such that it becomes possible to
3 Application estimate “the kind of
Development
entity that can be fetched using this method”.
4. There must be an argument for each search condition. However, when
there are many conditions, DTO containing all search conditions can
be provided.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: In case of methods related to update processing, it is recommended to construct methods in the
same way as shown above. “find” in the method name above can be replaced by “update” or “delete”.
• Todo.java (Entity)
• TodoRepository.java
(1) Example of method that fetches TODO objects whose title matches with specified value
(TODO in which todoTitle=[argument value]).
Physical name(todoTitle) of condition field is specified after findOneBy.
(2) Example of method that fetches unfinished TODO objects (TODO objects where
finished=false).
Logical condition name is specified after findAllBy.
(3) Example of method that fetches pages of unfinished TODOs (TODO objects where
finished=false).
Logical condition name is specified after findPageBy.
(4) Example of method that fetches count of TODO objects for which the finish deadline has
already passed (TODO for which createdAt < sysdate - [finish deadline in days] &&
finished=false).
Logical condition name is specified after countBy.
(5) Example of method that checks whether a TODO is created on a specific date
(createdAt=specified date).
Physical name (createdAt) is specified after existsBy.
Creation of RepositoryImpl
Roles of Service
Business logic consists of create, update, consistency check etc of business data as well as all the
processes related to business logic.
Create and update process of business data should be delegated to Repository(or O/R Mapper) and service
should be limited to implementation of business rules.
In this guideline, the logic to be implemented by Controller and Service should be as per the rules given
below.
1. For the data requested from the client, single item check and correlated item check is to be performed
in Controller (Bean Validation or Spring Validator).
2. Conversion processes (Bean conversion, Type conversion and Format conversion) for the data to be
passed to Service, must be performed in Controller instead of Service.
4. Conversion processes (Type conversion and Format conversion) for the data received from Service
(data to respond to the client), must be performed in Controller (View class etc).
Service consists of Service classes and SharedService classes and plays the following role.
In this guideline, POJO (Plain Old Java Object) having @Service annotation is defined as Service or
SharedService class.
We are not preventing the creation of interface and base classes that limit the signature of methods.
2 SharedService
1. Methods of other SharedService classes can
class
Provides shared (reusable) be called from a SharedService (Figure 2-
logicfor multiple Controllers 1). However, Calling hierarchy should
and Service classes. not become complicated. If calling hier-
archy becomes complicated, there is a risk
of reduction in maintainability.
2. Methods of SharedService classes can be
called from Controller (Figure 2-2). How-
ever, it can be only be done if there is
no problem from transaction manage-
ment perspective.If there is a problem
from transaction management perspective,
first create a method in Service class and
implement transaction management in this
method.
3. It is prohibited to call methods of Service
class from SharedService (Figure 2-3).
Logic that cannot be (should not be) reused and logic that can be (should be) reused exist in the business logic.
To implement these 2 logics in the same class, it is difficult to decide whether a method can be re-used or not.
To avoid this problem, it is strongly recommended to implement the method to be re-used in the
SharedService classin this guideline.
Reason for prohibiting the calling of other Service classes from Service class
In this guideline, calling methods of other Service classes from a Service class is prohibited.
Service provides business logic to a specific controller and is not created with the assumption of using it from
other services.
If it is called directly from other Service classes, the following situations can easily occur and there is a risk of
reduced maintainability.
1.
The logic that must be implemented in the calling service class, gets implemented in the called
service class for reasons like “having the logic at a single location” etc.
As a result, arguments for identifying the caller, get added to the method easily;
Ultimately, the logic is incorrectly abstracted out as shared logic (like utilities). It results
into a modular structure without much insight.
2.
If the stack patterns or stack of services calling each other is large in number, understanding
the impact of modifications in source-code due to change in specifications or bug fixes,
becomes difficult.
In order to bring consistency in development of business logic, interfaces and base classes are created which limit
the signature of the methods.
The purpose is also to prevent the injection of differences due to development style of each developer by limiting
the signature through interfaces and base classes.
Note: In large scale development, there are situations where not every single developer is highly skilled
or situations like having consistency in development of business logic considering maintainability after
servicing. In such situations, limiting the signature through interfaces can be an appropriate decision.
In this guideline, we do not specifically recommended to create interface to limit signature; however, type
of architecture must be selected on the basis of characteristics of the project. decide the type of architecture
taking into account the project properties.
Note: Sample of implementation of interface and base classes to limit signature - Interface to limit
signature
// (1)
public interface BLogic<I, O> {
O execute(I input);
}
• Controller
// (2)
@Inject
XxxBLogic<XxxInput, XxxOutput> xxxBLogic;
// (3)
XxxOutput output = xxxBlogic.execute(input);
// omitted
redirectAttributes.addFlashAttribute(output.getTourReservation());
return "redirect:/xxx?complete";
}
(3) Controller calls execute method of BLogic interface and executes business logic.
To standardize process flow of business logic when a fixed common process is included in Service, base
classes are created to limit signature of method.
// omitted
// (4)
preExecute(input);
// (5)
O output = doExecute(input);
// omitted
return output;
} finally {
// omitted
}
(4) Call the method to perform pre-processing before executing business logic from base
classes.
In the preExecute method, business rules are checked.
(5) Call the method executing business logic from the base classes.
// (6)
protected void preExecute(XxxInput input) {
// omitted
Tour tour = tourRepository.findOne(input.getTourId());
Date reservationLimitDate = tour.reservationLimitDate();
if(input.getReservationDate().after(reservationLimitDate)){
throw new BusinessException(ResultMessages.error().add("e.xx.xx.0001"));
}
// (7)
protected XxxOutput doExecute(XxxInput input) {
TourReservation tourReservation = new TourReservation();
// omitted
tourReservationRepository.save(tourReservation);
XxxOutput output = new XxxOutput();
output.setTourReservation(tourReservation);
// omitted
return output;
}
1.
For each Entity Create Service paired with Main Entity is in other words, business data. If the
the main Entity. application is to be designed and implemented
with focus on business data, then Service classes
should be created in this way.
2.
For each Create Service paired with If the application is to be designed and
use-case the use-case. implemented with focus on events on the screen,
Service should be created in this way.
3.
For each event Create Service paired with If the application is to be designed and
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: The pattern of Service creation must be decided by taking into account the features of
application to be developed and the structure of development team.
It is not necessary to narrow down to any one pattern out of the 3 indicated patterns. Creating Ser-
vices using different patterns randomly should be avoided for sure; however, patterns can be used in
combinations, if policy of usage of patterns in certain specific conditions has been well-thought
decision and has been directed by the architect. For example, the following combinations are possi-
ble.
[Example of usage of patterns in combination]
• For the business logic very important to the whole application, create as SharedService class for
each Entity.
• For the business logic to be processed for the events from the screen, create as Service class for
each Controller.
• In the Service class for each controller, implement business logic by calling the sharedService as
and when required.
Tip: BLogic is generated directly from design documents when using “TERASOLUNA ViSC”.
Following is the image of application development when creating a Service for each Entity.
Note: An example of a typical application in which a Service is created for each Entity is a REST applica-
tion. REST application provides CRUD operations (POST, GET, PUT, DELETE of HTTP) for published
resources on HTTP. Most of the times, the resources published on HTTP are business data (Entity) or part
of business data (Entity), they have good compatibility with the pattern of creating Service for each Entity.
In case of REST application, most of the times, use-cases are also extracted on a “per Entity” basis. Hence,
the structure is similar to the case when Service is created for on a “per use-case” basis.
(2) Implement SharedService if there is shared logic between multiple business logics.
In the above figure, different person is assigned as the in-charge. However, he may be the same
person as (1) depending per the project structure.
Following is the image of application development when creating a Service for each use-case.
In case of use-case which performs CRUD operations on the Entity, structure is same as in case of creating
(2) Implement SharedService if there is shared logic between multiple business logics.
In the above figure, different person is assigned as the in-charge. However, he may be the same
person as (1) depending per the project structure.
Note: With an increase in the size of the use-cases, the development scope of a person increases. At
such a point of time, it becomes difficult to divide the work of this use-case with other developers. In case
of application which has to be developed by introducing a large number of developer at the same time,
the use-case can be further split into finer use-cases and which can then be allocated to more number of
developers.
Below is the image of application development when the use-case is further split.
Splitting a use-case has no impact on SharedService. Hence, the explanation is omitted here.
(1) Divide the use-case into finer processes which make-up the complete use-case. Assign each
fine process to a developer. Each developer creates the Service for assigned process.
Note that the processes here are operations like search, create, update, delete etc. and these
processes do not have a direct mapping to the processing required to be done for
each event generated on screen.
For example, if it the event generated on screen is “Update”, it includes multiple finer processes
such as “Fetching the data to be updated”, “Compatibility check of update contents” etc.
If there is no specific reason, it is desirable that Controller must also be created for each of these
finer processes and must be developed by the same developer who creates the Service class.
Tip: In some projects, “group of use-cases” and “use-cases” are used in place of “use-case” and “pro-
cesses” used in this guideline.
Following is the image of application development when creating a Service(BLogic) for each event.
(2) If there is no specific reason, controller also should be created on “per use-case” basis.
(3) Even if the separate Service(BLogic) is created for each event, it is recommended that same
person is the in-charge of the complete use-case.
3.2. Domain Layer Implementation 167
(4) Implement in SharedService to share the logic with multiple business logics.
In the above figure, different person is assigned as the in-charge. However, he may be the same
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: With an increase in the size of the use-cases, the development scope of a person increases. At
such a point of time, it becomes difficult to divide the work of this use-case with other developers. In case
of application which has to be developed by introducing a large number of developer at the same time,
the use-case can be further split into finer use-cases and which can then be allocated to more number of
developers.
Below is the image of application development when the use-case is further split.
Splitting a use-case has no impact on SharedService. Hence, the explanation is omitted here.
(1) Divide the use-case into finer processes which make-up the complete use-case. Assign each
fine process to a developer. Each developer creates the Service for assigned process.
Note that the processes here are operations like search, create, update, delete etc. and these
processes do not have a direct mapping to the processing required to be done for
each event generated on screen.
For example, if it the event generated on screen is “Update”, it includes multiple finer processes
such as “Fetching the data to be updated”, “Compatibility check of update contents” etc.
If there is no specific reason, it is desirable that Controller must also be created for each of these
finer processes and must be developed by the same developer who creates the Service class.
Below are the points to be taken care of while creating Service class.
1. If interface is there, When using AOP, Dynamic proxies functionality of standard JDK is used. In
case of no interface, CGLIB included in Spring Framework is used. In case of CGLIB there are certain
restrictions like “Advice cannot be applied on final methods” etc. Refer to Spring Reference Document
-Aspect Oriented Programming with Spring(Proxying mechanisms)-for details.
2. It becomes easier to create a stub of business logic. When application layer and domain layer are
developed in parallel using different development teams, stubs of Service are required. When there is
a need to create stubs, it is recommended to have interface .
@Service // (1)
@Transactional // (2)
public class CartServiceImpl implements CartService { // (3) (4)
// omitted
}
(4) Service class must not maintain state. Register it in container as bean of singleton scope .
Objects (POJO such as Entity/DTO/VO) and values (primitive type, primitive wrapper class)
where state changes in each thread should not be maintained in class level fields.
Setting scope to any value other than singleton (prototype, request, session) using @Scope
annotation is also prohibited.
Transaction boundary is required only for the business logic that updates the database. However, it is
recommended to apply the annotation at class level to prevent bugs due to skipped annotation. However,
defining @Transactional annotation only at required places (methods which update the database) is
also fine.
1. prototype, request, session are the scopes for registering bean that maintains state. Hence they must
not be used in Service class.
2. When scope is set to request or prototype, performance is affected as the bean generation frequency is
high in DI container.
3. When scope is set to request or session, it cannot be used in non Web applications (for example, Batch
application).
Below are the points to be taken care of while writing methods of Service class.
@Service
@Transactional
public class CartServiceImpl implements CartService {
@Inject
CartRepository cartRepository;
(2) Define methods in Service interface and implement business logic in its implementation
class.
(3) Add @Transactional annotation for changes to default transaction definition (class level
annotation).
Attributes should be specified as per the requirement.
Refer to Information required for “Declarative transaction management” for details.
The way of handling read-only transactions depends on the implementation of JDBC driver; hence, confirm
the specifications of JDBC driver to be used.
If it is set to “perform health check” when retrieving a connection from connection pool, “Read-only
transactions” may not be enabled. For details on this event and to avoid the same, refer to About cases
where “Read-only transactions” are not enabled.
The below points must be considered for arguments and return values of methods of Service class.
Serializable classes (class implementing java.io.Serializable) must be used for arguments and return
values of Service class.
Since there is possibility of Service class getting deployed as distributed application, it is recommended to allow
only Serializable class.
• void
• etc ...
1. Input object indicates the object that has all the input values required for executing Service method.
2. Output object indicates the object that has all the execution results (output values) of Service method.
If business logic(BLogic class) is generated using “TERASOLUNA ViSC” then, input and output
objects are used as argument and return value of the of BLogic class.
Values that are forbidden as arguments and return values are as follows.
• Objects (javax.servlet.http.HttpServletRequest ,
javax.servlet.http.HttpServletResponse , javax.servlet.http.HttpSession
, org.springframework.http.server.ServletServerHttpRequest) which are de-
pendent on implementation architecture of application layer (Servlet API or web layer API of
Spring).
1. If objects depending on implementation architecture of application layer are allowed, then application
layer and domain layer get tightly coupled.
2. java.util.Map is too generalized. Using it for method arguments and return values makes it
difficult to understand what type of object is stored inside it. Further, since the values are managed
using keys, the following problems may occur.
• Values are mapped to a unique key and hence cannot be retrieved by specifying a key name which is
different from the one specified at the time of inserting the value.
• When key name has to be changed, it becomes difficult to determine the impacted area.
How to sharing the same DTO between the application layer and domain layer is shown below.
• DTO belonging to the package of domain layer can be used in application layer.
Warning: Form and DTO of application layer should not be used in domain layer.
Below are the points to be taken care of while creating SharedService class.
Only the points which are different from Service class are explained here.
Below are the points to be taken care of while writing methods of SharedService class.
Only the points which are different from Service class are explained here.
1. Methods in SharedService class must be created for each logic which is shared between multiple
business logics.
Points are same as Regarding arguments and return values of methods of Service class.
Implementation of logic
Service and SharedService has implementation of logic related to operations such as data fetch, update, consis-
tency check of business data and implementation related to business rules.
Refer to the following for the examples of data (Entity) fetch and update.
Returning messages
Warning message and business error message are the two type of messages which must be resolved in Service
(refer to the figure in red broken line below).
Other messages should be resolved in application layer.
Refer to Message Management for message types and message pattern.
In service, instead of the actual message the information required for building the message (message
code, message insert value) is resolved.
Message object must be returned for warning message. If domain object such as Entity needs to be returned with
it,
message object and domain object should inserted into output object (DTO) and this output object must be
returned.
• Creation of DTO
// omitted
// omitted
// omitted
// omitted
(1) When the order includes products which are not available right now, set hasOrderProduct
to true.
(2) In the above example, when the order includes products which are not available right now, a
warning message occurs.
(3) In the above example, the registered Order object and warning message are returned by
storing objects in a DTO called OrderResult.
Business exception is thrown when business rules are violated while executing business logic.
The following can be the cases.
• etc ...
Since business exceptions need to be handled in controller class, they can be configured as checked ex-
ception. However in this guideline, it is recommended that business exception be subclass of unchecked
exception (java.lang.RuntimeException). By default, if there is a RuntimeException, transaction
will be rolled back. Hence, doing this will prevent leaving a bug in the source-code due to inadequate
settings of @Transactional annotation. Obviously, if settings are changed such that transaction rollbacks
even in case checked exceptions, business exception can be configured as subclass of checked exceptions.
// omitted
if(currentDate.after(reservationLimitDate)) { // (1)
throw new BusinessException(ResultMessages.error().add("e.xx.xx.0001"));
}
// omitted
System exception is thrown when error occurs in system while executing business logic.
The following can be the cases.
• When master data, directories and files that should already exist, do not exist
• When a checked exception generated by a library method is caught and this exception indicates abnormal
system state.
• etc ...
Example that throws system exception while catching IO exception while copying the file is shown below.
// ...
try {
FileUtils.copy(srcFile, destFile);
} catch(IOException e) { // (1)
throw new SystemException("e.xx.fw.0002",
"Failed file copy. src file '" + srcFile + "' dest file '" + destFile + "'.", e);
}
(1) System exception that is classified into invalid system state is thrown by the library method.
The exception generated by library must be passed to system exception class as cause
exception.
If cause exception is lost, error occurrence location and basic error cause cannot be traced from
the stack trace.
When data access error occurs in Repository and O/R Mapper while executing business logic, it is con-
verted to subclass of org.springframework.dao.DataAccessException and thrown. Error
can be handled in application layer instead of catching in business logic. However, some errors like unique
constraints violation error should be handled in business logic as per business requirements. Refer to
Database Access (Common) for details.
Transaction management is required in the logic where data consistency must be ensured.
There are various transaction management methods. However, in this guideline, it is recommended to use
“Declarative Transaction Management” provided by Spring Framework.
In “Declarative transaction management”, the information required for transaction management can be declared
by the following 2 methods.
Refer to Spring Reference Document -Transaction Management(Declarative transaction management)- for the
details on “Declarative type transaction management” provided by Spring Framework.
1. The transaction management to be performed can be understood by just looking at the source code.
2. AOP settings for transaction management is not required if annotations are used and so XML becomes
simple.
Specify @Transactional annotation for at class level or method level which are considered as target of
transaction management and specify the information required for
transaction control in attributes of @Transactional annotation.
However, in this guideline, it is recommended to use an annotation of Spring Framework that can specify
the information required for “Declarative transaction management” in a much more detailed way.
[REQUIRED]
Starts transaction if not started. (default when omitted)
[REQUIRES_NEW]
Always starts a new transaction.
[SUPPORTS]
Uses transaction if started. Does not use if not started.
[NOT_SUPPORTED]
Does not use transaction.
[MANDATORY]
Transaction should start. An exception occurs if not started.
[NEVER]
Does not use transaction (never start). An exception occurs if started.
[NESTED]
save points are set. They are valid only in JDBC.
2 isolation
[DEFAULT]
Isolation level provided by DB by default.(default when omitted)
[READ_UNCOMMITTED]
Reads (uncommitted) data modified in other transactions.
[READ_COMMITTED]
Does not read (uncommitted) data modified in other transactions.
[REPEATABLE_READ]
Data read by other transactions cannot be updated.
[SERIALIZABLE]
Isolates transactions completely.
3 timeout
It is recommended to specify the annotation at the class level or method level of the class. Must
be noted that it should not interface or method of interface. For the reason, refer to 2nd Tips of Spring
Reference Document -Transaction Management(Using @Transactional)-.
There is a value attribute in @Transactional annotation. However, this attribute specifies which
Transaction Manager to be used when multiple Transaction Managers are declared. It is not required
to specify when there is only one Transaction Manager. When multiple Transaction Managers need to be
used, refer to Spring Reference Document -Transaction Management(Multiple Transaction Managers with
@Transactional)-.
• Oracle : READ_COMMITTED
• DB2 : READ_COMMITTED
• PostgreSQL : READ_COMMITTED
• MySQL : REPEATABLE_READ
A mechanism is provided to run SQL under “Read-only transactions” by specifying readOnly = true;
however, when all of the following conditions are satisfied, there will be a JDBC driver where “Read-only
transactions” are not enabled.
In a case where “Read-only transactions” are not enabled, if readOnly = true is specified, it ends up
carrying out the unnecessary processes. Therefore, it is recommended to execute the SQL under “Updatable
transactions” even for the reference processes.
• Do not perform health check when retrieving a connection from connection pool.
• Enable ‘Auto commit’ of the connection retrieved from connection pool. (Disable ‘Auto commit’ only
when transaction management is required)
However, However, do not change the design for ‘health check’ and ‘auto commit’ to avoid this event.
[Remarks]
• Reproduction of this event is confirmed on PostgreSQL 9.3 and Oracle 12c. It is not performed on any
other database and versions.
• When SQL or API call of JDBC is logged in using log4jdbc, SQLException occurred from JDBC
driver is output to log with ERROR level.
• SQL Exception occurred from JDBC driver is ignored by exception handling of Spring Frame-
work. Hence, even though it is not an error as application behavior, the “Read-only transactions”
is not enabled.
[Reference]
When following log is output using log4jdbc, it will be treated as a case corresponding to this event.
Propagation of transaction
1. Controller calls a method of Service class. At this time, since started transaction does not exist, transaction
is started using TransactionInterceptor.
2. TransactionInterceptor calls the method of service class after starting the transaction.
3. Service class calls a method of SharedService. This method is also under transaction control. At this
time, though started transaction exists, TransactionInterceptor participates in the started transac-
tion without starting a new transaction.
4. TransactionInterceptor calls the method under transaction control after participating in the started
transaction.
When propagation method of transaction is set to “REQUIRED”, though there is only one physical transaction, in-
ternally Spring Framework creates transaction boundaries. In case of above example, when a method of SharedSer-
vice is called, a TransactionInterceptor is started which internally provides transaction control boundary at Shared-
Service level. Therefore, when an exception (which is set as target of rollback) occurs in SharedService
method, status of transaction is set to rollback (rollback-only) by TransactionInterceptor. This trans-
action now cannot be committed. Going further, if the Service method tries to commit this transaction
due to conflicting settings of rollback target exception between Service method and SharedShared method,
UnexpectedRollbackException is generated by Spring Framework notifying that there is inconsistency
in transaction control settings. When UnexpectedRollbackException is generated, it should be checked that there
is no inconsistency in rollbackFor and noRollbackFor settings.
1. Controller calls a method of Service class. This method is under transaction control. At this time, since
started transaction does not exist, transaction is started by TransactionInterceptor (Hereafter, the
started transaction is referred as “Transaction A”).
3. Service class calls a method of SharedService class. At this time, though started transaction (Transac-
tion A) exists, since propagation method of transaction is “REQUIRES_NEW”, new transaction is started
5. TransactionInterceptor performs commit or rollback according to the process result and ends the
Transaction B. At this time, “Transaction A” is resumed and status is changed to Active.
6. TransactionInterceptor performs commit or rollback according to the process result and ends the
Transaction A.
Since “Declarative transaction management” provided by Spring Framework is implemented using AOP,
transaction management is applied only for method calls for which AOP is enabled.
Since the default mode of AOP is “proxy” mode, transaction control will be applied only when public
method is called from another class.
Note that transaction control is not applied if the target method is called from an internal method even if
the target method is a public method.
It is possible to enable transaction control for internal method calls as well by setting the AOP mode
to "aspectj". However, if internal method call of transaction management is enabled, the route of
transaction management may become complicated; hence it is recommended to use the default “proxy” for
AOP mode.
PlatformTransactionManager settings
• xxx-env.xml
Example of settings for managing the transaction using JDBC connection which is fetched from DataSource
is given below.
Note: When transaction management (Global transaction management) is required for multiple
DBs (Multiple resources)
1.
Implementation class for managing the transaction by
org.springframework.jdbc.datasource.
calling API of JDBC(java.sql.Connection).
DataSourceTransactionManager
Use this class when MyBatis and JdbcTemplate is to
be used.
2.
org.springframework.orm.jpa. Implementation class for managing the transaction by
JpaTransactionManager calling API of
JPA(javax.persistence.EntityTransaction).
Use this class when JPA is to be used.
3.
org.springframework.transaction.jta. Implementation class for managing the transaction by
JtaTransactionManager calling API of
JTA(javax.transaction.UserTransaction).
Use this class to manage transaction with resources
(Database/Messaging service/General-purpose
EIS(Enterprise Information System) etc.) using JTS (Java
Transaction Service) provided by application server.
When it is necessary to execute the operations with
multiple resources in a single transaction, it is necessary
to use JTA for managing transactions.
• xxx-domain.xml
Since the article of IBM DeveloperWorks is an old article (year 2009), some of the content is different than
the behavior when using Spring Framework 4.1.
Specifically, the contents of “Listing 7. Using read-only with REQUIRED propagation mode - JPA”.
From Spring Framework 4.1, when Hibernate ORM 4.2 or higher version is used as JPA provider, it has
been improved so that instruction can be given to run the SQL under “Read-only transactions” for JDBC
driver (SPR-8959).
The way of handling read-only transactions depends on the implementation of JDBC driver; hence, confirm
the specifications of JDBC driver to be used.
Various attributes can be specified in <tx:annotation-driven> and default behavior can be customized.
• xxx-domain.xml
<tx:annotation-driven
transaction-manager="txManager"
mode="aspectj"
proxy-target-class="true"
order="0" />
4 order Order of Advice of AOP (Priority). When omitted, it will be “Last (Lowest
priority)”.
3.2.7 Tips
When it is necessary to output the error of business rules for each field, the mechanism of (Bean Validation or
Spring Validator) on the Controller side should be used.
In this case, It is recommended to implement check logic as Service class and then to call the method of Service
class from Bean Validation or Spring Validator.
Refer to Input Validation business logic approach for details.
Methods to create a Repository for relational database using MyBatis3 and JPA are introduced below.
When MyBatis3 is to be used as persistence API with relational database, RepositoryImpl need not be imple-
mented, if Repository interface is created using “Mapper interface mechanism” provided by MyBatis3.
This is because it is a mechanism where MyBatis3 automatically maps the method of Mapper interface and the
statement (SQL) to be called.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
// (1)
public interface TodoRepository {
// (2)
Todo findOne(String todoId);
}
</mapper>
Define statement (SQL) to be run for each method defined in Repository interface.
Specify a method name of Repository interface in statement ID of each statement element (id
(4)
attribute of select/insert/update/delete element).
When JPA is to be used as persistence API with relational database, Repository can be very easily created if
org.springframework.data.jpa.repository.JpaRepository of Spring Data JPA is used.
For details on how to use Spring Data JPA, refer to Database Access (JPA).
When Spring Data JPA is used, only an interface with inherited JpaRepository is required to be created for basic
CRUD operations. In other words, RepositoryImpl is not required.
However, RepositoryImpl is needed for using dynamic query (JPQL).
Refer to Database Access (JPA) for implementing RepositoryImpl when using Spring Data JPA.
• TodoRepository.java
Describe the case to add operations which are not provided by JpaRepository.
When Spring Data JPA is used, if it is a static query, it is advisable to add a method to the interface and to specify
the query (JPQL) to be executed when that method is called, using the annotation.
• TodoRepository.java
(1)
Todo
TBD
This chapter explains implementation of application layer of a web application that uses HTML forms.
Note: Refer to the following page for the implementation required for Ajax development and REST API.
• Ajax
1. Implementing Controller
Controller receives the request, calls business logic, updates model, decides View. Thereby, controls one
complete round of operations after receiving the request.
It is the most important part in the implementation of application layer.
3. Implementing View
View (JSP) acquires the data from model (form object, domain object etc.) and generates screen (HTML).
Controller does not implement business logic but delegates by calling Service method.
Note: It is recommended that controller implements only the routing logic such as calling business logic,
reflecting output of the business logic to Model, deciding the View name is implemented in the Controller.
Controller class is created with @Controller annotation added to POJO class (Annotation-based
Controller).
Controller in Spring MVC can also be created by implementing
org.springframework.web.servlet.mvc.Controller interface (Interface-based Controller).
However, it is preferred to avoid using it as it is Deprecated from Spring 3 onwards.
@Controller
public class SampleController {
// ...
}
@RequestMapping(value = "hello")
public String hello() {
// ...
}
The rules for mapping the incoming request with a handler method can be specifying as attributes of
@RequestMapping annotation.
method
2.
Specify HTTP method (RequestMethod type) which needs to be mapped
(multiple methods allowed).
GET/POST are mainly used for mapping requests from HTML form, while other
HTTP methods (such as PUT/DELETE) are used for mapping requests from REST
APIs as well.
params
3.
Specify request parameters which need to be mapped (multiple parameters allowed).
Request parameters are mainly used for mapping request from HTML form. If this
mapping method is used, the case of mapping multiple buttons on HTML page can
be implemented easily.
headers
4.
Specify request headers which need to be mapped (multiple headers allowed).
Mainly used while mapping REST API and Ajax requests.
consumes
5.
Mapping can be performed using Content-Type header of request. Specify media
type which needs to be mapped (multiple types allowed).
Mainly used while mapping REST API and Ajax requests.
produces
6.
Mapping can be performed using Accept header of request. Specify media type
which needs to be mapped (multiple types allowed).
Mainly used while mapping REST API and Ajax requests.
Complex mapping can be performed by combining multiple attributes, but considering maintainability,
mapping should be defined and designed in the simplest way possible . It is recommended to consider
combining 2 attributes (value attribute and any other 1 attribute).
In the following explanation, it is prerequisite to define the handler method in the Controller class.
@Controller // (1)
@RequestMapping("sample") // (2)
public class SampleController {
// ...
}
All the handler methods in this class are mapped to URLs with “sample” by adding
@RequestMapping("sample") annotation at class level.
(2)
In case of the following definition, if the URL "sample/hello" is accessed, then hello() method is exe-
cuted.
@RequestMapping(value = "hello")
public String hello() {
Pattern can be specified instead of a specific value for request path. For details of specifying patterns, refer to
reference documentation of Spring Framework.
• Path Patterns
In case of the following definition, if the URL "sample/hello" is accessed with POST method, then
hello() method is executed. For the list of supported HTTP methods, refer to Javadoc of RequestMethod.
When not specified, all supported HTTP methods are mapped.
In case of following definition, if the URL "sample/hello?form" is accessed, then hello() method is
executed.
When request is sent as a POST request, request parameters may exist in request body even if they do not exist in
URL.
Refer to the details on the following page to mainly use the controller to map REST API and Ajax requests.
• Ajax
Refer to the details on the following page to mainly use the controller to map REST API and Ajax requests.
• Ajax
Refer to the details on the following page to mainly use the controller to map REST API and Ajax requests.
• Ajax
• Grouping of URL of request is done for each unit of business flow or functional flow.
URL grouping means defining @RequestMapping(value = "xxx") as class level annotation.
• Use the same URL for requests for screen transitions within same functional flow
The same URL means the value of ‘value’ attribute of @RequestMapping(value = "xxx") must
be same.
Determining which handler method is used for a particular request with same functional flow is performed
using HTTP method and HTTP parameters.
The following is an example of mapping between incoming request and handler method by a sample application
with basic screen flow.
• Request URL
Create Entity Create a new Entity with the specified contents. Screen flow (form screen,
2.
confirmation screen, completion screen) exists for this process.
Entity update Update Entity of specified ID. Screen flow (form screen, confirmation
4.
screen, completion screen) exists for this process.
Request URL
• Request URLs of all the requests required by the process flow are grouped.
This functionality performs CRUD operations of Entity called ABC, therefore URL that starts with
"/abc/" is considered.
Note: "{id}" specified in URL of ‘Fetching details of Entity’, ‘Entity update’, ‘Entity delete’ operations
is called as, URI Template Pattern and any value can be specified. In this sample application, Entity ID is
specified.
Assigned URL of each operation of screen flow diagram is mapped as shown below:
Figure.3.3 Picture - Screen flow of entity management function and corresponding assigned URL
Multiple requests exist for each of Create Entity, Entity Update and Entity Delete functions. Therefore switching
(1)
(2)
Note: In this handler method, method attribute is not specified since it is not required for HTTP GET
method.
Besides implementing the handler method for form display, points mentioned below are required:
• Implement generation process of form object. Refer to Implementing form object for the details of form
object.
• Implement View of form screen. Refer to Implementing View for the details of View.
@NotEmpty
private String input1;
@NotNull
@Min(1)
@Max(10)
private Integer input2;
// omitted setter&getter
}
@ModelAttribute
public AbcForm setUpAbcForm() {
return new AbcForm();
}
(1)
To check user input in the form, data is sent by POST method and confirm is specified as HTTP parameter.
(1)
In case of input validation errors, it is recommended to call the handler method of form re-
display.
(2)
Return view-name of JSP to render the screen for user input confirmation.
(3)
Note: POST method is specified to prevent displaying confidential information such as password and other
personal information etc. in the address bar. (Needless to say that these security measures not sufficient
and needs more secure measures such as SSL etc.)
Besides implementing handler method for user input confirmation screen, points mentioned below are required.
• Implement view of user-input confirmation screen. Refer to Implementing View for the details of view.
(2)
Parameter name need not be specified for submit button. Submit button will do the actual create
operation.
(3)
Note: In the above example, HTML escaping is performed as an XSS countermeasure using f:h()
function while displaying the user input values. For details, refer to Cross Site Scripting.
Since HTTP parameters are sent across through HTTP POST method after clicking the Confirm button, it does
not appear in URI. However, “confirm” is included as HTTP parameter.
(1)
(2)
Since HTTP parameters are sent across through HTTP POST method after clicking the Back button, it does not
appear in URI. However, “redo” is included as HTTP parameter. Moreover, since input values of form had been
sent as hidden fields, input values can be restored on redisplayed form screen.
• Behavior when page having Back button is accessed and Back button is clicked.
• History of browser
To register input contents of form, the data (hidden parameters) to be registered is sent with HTTP POST method.
Sorting is not carried out using HTTP parameters since new request will be the main request of this operation.
Since the state of database changes in this process, it should not be executed multiple times due to double
submission.
Therefore, it is ‘redirected’ to the next screen (create complete screen) instead of directly displaying View
(screen) after
completing this process. This pattern is called as POST-Redirect-GET(PRG) pattern. For the details of PRG
(Post-Redirect-Get) pattern
refer to Double Submit Protection .
(1)
Return URL to the request needs to be redirected as view name in order to use PRG pattern.
(2)
Warning: PRG pattern is used to avoid double submission when the browser gets reloaded by clicking F5
button. However, as a countermeasure for double submission, it is necessary to use TransactionTokenCheck
functionality. For details of TransactionTokenCheck, refer to Double Submit Protection .
‘Create’ request does not return to the screen directly, but it is redirected to create complete display
("/abc/create?complete"). Hence HTTP status is changed to 302.
In order to notify the completion of create process, complete must be present in the request as HTTP parameter.
(1)
(2)
Note: In this handler method, method attribute is not specified since it is not required for HTTP GET
method.
Note: Since PRG pattern is used, even if browser is reloaded, create completion screen is only re-displayed
To place multiple buttons on a single form, send HTTP parameter to identify the corresponding button and so
that the handler method of controller can be switched. An example of Create button and Back button on input
confirmation screen of sample application is explained here.
‘Create’ button to perform ‘user creation’ and ‘Back’ button to redisplay ‘create form’ exists on the form of input
confirmation screen as shown below.
To redisplay ‘create form’ using request ( "/abc/create?redo" ) when Back button is clicked, the following
code is required in HTML form.
For the operations when Back button is clicked, refer to Implementing ‘redisplay of form’.
Source-code of controller after implementing create process of sample application are shown below.
Fetching list of Entities, Fetching detail of Entity, Entity update, Entity delete are implemented using the same
guidelines.
@Controller
@RequestMapping("abc")
public class AbcController {
@ModelAttribute
public AbcForm setUpAbcForm() {
return new AbcForm();
}
Model model) {
if (result.hasErrors()) {
return createRedo(form, model);
}
// omitted
return "abc/createConfirm";
}
The arguments of handler method can be used to fetch various values; however, as a principle rule, the following
should not be fetched using arguments of handler method of controller.
• ServletRequest
• HttpServletRequest
• org.springframework.web.context.request.WebRequest
• org.springframework.web.context.request.NativeWebRequest
• java.io.InputStream
• java.io.Reader
• java.io.OutputStream
• java.io.Writer
• java.util.Map
• org.springframework.ui.ModelMap
Note: When generalized values like getAttribute/setAttribute of HttpServletRequest and get/put of Map
are allowed, liberal use of these can degrade the maintainability of the project with an increase in project size.
For the above reason, using HttpSession is not reccomended in the case when there are alternatives.
When common parameters (request parameters) need to be stored in JavaBean and passed as an argument to a
method of controller, it can be implemented using Implementing HandlerMethodArgumentResolver as described
later.
• SampleController.java
@RequestMapping("hello")
public String hello(Model model) { // (1)
model.addAttribute("hello", "Hello World!"); // (2)
model.addAttribute(new HelloBean("Bean Hello World!")); // (3)
return "sample/hello"; // returns view name
}
• hello.jsp
(1)
(2) Call addAttribute method of Model object received as argument, and add the data to
Model object.
For example, "HelloWorld!" string is added to the attribute name "hello".
(3) If first argument of addAttribute method is omitted, the class name beginning with lower
case letter will become the attribute name.
For example, the result of model.addAttribute("helloBean", new
HelloBean()); is same as the result of model.addAttribute(new
HelloBean());
(4) In View (JSP), it is possible to acquire the data added to Model object by specifying
“${Attribute name}”.
For example, HTML escaping is performed using “${f:h(Attribute name)}” function of EL
expression.
For details of functions of EL expression that perform HTML escaping, refer to Cross Site
Scripting.
(5) The values of JavaBean stored in Model can be acquired by specifying “${Attribute
name.property name}”.
Note: Even though the Model is not used, it can be specified as an argument. Even if it is not required
at the initial stage of implementation, it can be used later (so that the signature of methods need not be
changed in future).
Note: The value can also be referred from the module which is not managed under Spring MVC
To retrieve values from URL path, add @PathVariable annotation to argument of handler method of
controller.
In order to retrieve values from the path using @PathVariable annotation, value of @RequestMapping
annotation must contain those values in the form of variables (for example, {id}).
@RequestMapping("hello/{id}/{version}") // (1)
public String hello(
@PathVariable("id") String id, // (2)
@PathVariable Integer version, // (3)
Model model) {
// do something
return "sample/hello"; // returns view name
}
(1) Specify the portion to be extracted as path variable in the value of @RequestMapping
annotation. Specify path variable in “{variable name}” format.
For example, 2 path variables such as "id" and "version" are specified.
(3) Value attribute of @PathVariable annotation can be omitted. When it is omitted, the
argument name is considered as the request parameter name.
In the above example, when the URL "sample/hello/aaaa/1" is accessed, value "1" is
passed to argument “version”.
However, in this method compilation needs to be done by specifying either of:
Note: Binding argument can be of any data type other than string. In case of different data type,
org.springframework.beans.TypeMismatchException is thrown and default response is
400 (Bad Request). For example, when the URL "sample/hello/aaaa/v1" is accessed, an ex-
ception is thrown since "v1" cannot be converted into Integer type.
Warning: When omitting the value attribute of @PathVariable annotation, the application to be
deployed needs to be compiled by specifying -g option or -parameters option which is added
from Java8. When these options are specified, there is a likely impact on memory and processing
performance since information or processing required for debugging gets appended to the class after
compilation. Basically, it is recommended to explicitly specify the value attribute.
@RequestMapping("bindRequestParams")
public String bindRequestParams(
@RequestParam("id") String id, // (1)
@RequestParam String name, // (2)
@RequestParam(value = "age", required = false) Integer age, // (3)
@RequestParam(value = "genderCode", required = false, defaultValue = "unknown") Strin
Model model) {
// do something
return "sample/hello"; // returns view name
}
(1) Specify request parameter name in the value attribute of @RequestParam annotation.
For example, when the URL "sample/hello?id=aaaa" is accessed, the string "aaaa"
is passed to argument “id”.
(2) value attribute of @RequestParam annotation can be omitted. When it is omitted, the
argument name becomes the request parameter name.
For example, when the URL "sample/hello?name=bbbb&...." is accessed, string
"bbbb" is passed to argument “name”.
However, in this method compilation needs to be done by specifying either of:
(3) By default, an error occurs if the specified request parameter does not exist. When request
parameter is not required, specify false in the required attribute.
For example, when it is accessed where request parameter age does not exist, null is passed
to argument “age”.
(4) When default value is to be used if the specified request parameter does not exist, specify the
default value in defaultValue attribute.
For example, when it is accessed where request parameter genderCode does not exist,
"unknown" is passed to argument “genderCode”.
Note: Binding argument can be of any data type. In case the data type do not match,
org.springframework.beans.TypeMismatchException is thrown and default response is
400 (Bad Request). For example, when "sample/hello?age=aaaa&..." URL is accessed, ex-
Binding to form object must be done only when any of the following conditions are met.
• If request parameter is not an item in HTML form, however, input validation other than mandatory check
needs to be performed.
• If error details of input validation error needs to be output for each parameter.
• If there are 3 or more request parameters. (maintenance and readability point of view)
Following is an example that shows the difference between handler method that fetches each request parameter
using @RequestParam and the same handler method when fetching request parameters in a form object
Handler method that receives request parameter separately using @RequestParam is as shown below.
@RequestMapping("bindRequestParams")
public String bindRequestParams(
@RequestParam("id") String id,
@RequestParam String name,
@RequestParam(value = "age", required = false) Integer age,
@RequestParam(value = "genderCode", required = false, defaultValue = "unknown") Strin
Model model) {
// do something
return "sample/hello"; // returns view name
}
Note: Request parameter name should match with form object property name.
Make changes such that request parameters which were being fetched individually using
@RequestParam now get fetched as form object.
@RequestMapping("bindRequestParams")
public String bindRequestParams(@Validated SampleForm form, // (1)
BindingResult result,
Model model) {
// do something
return "sample/hello"; // returns view name
}
(1)
Note: When form object is used as argument, unlike @RequestParam, mandatory check is not per-
formed. When using form object, Performing input validation should be performed as described below.
Warning: Domain objects such as Entity, etc. can also be used as form object without any changes required.
However, the parameters such as password for confirmation, agreement confirmation checkbox, etc. should
exist only on WEB screen. Since the fields depending on such screen items should not be added to domain
objects, it is recommended to create class for form object separate from domain object. When a domain object
needs to be created from request parameters, values must first be received in form object and then copied to
domain object from form object.
When performing input validation for the form object, add @Validated annotation to form object ar-
gument, and specify org.springframework.validation.BindingResult (Hereafter called as
BindingResult) to argument immediately after form object argument.
Add annotations required in input validation to the fields of form object class.
@NotNull
@Size(min = 10, max = 10)
private String id;
@NotNull
@Size(min = 1, max = 10)
private String name;
@Min(1)
@Max(100)
private Integer age;
@RequestMapping("bindRequestParams")
public String bindRequestParams(@Validated SampleForm form, // (1)
BindingResult result, // (2)
Model model) {
if (result.hasErrors()) { // (3)
return "sample/input"; // back to the input view
}
// do something
return "sample/hello"; // returns view name
}
(2)
(3)
To redirect after executing a handler method ofcontroller and to pass data along with it, fetch
org.springframework.web.servlet.mvc.support.RedirectAttributes (Henceforth called
as RedirectAttributes) as an argument of handler method, and add the data to RedirectAttributes
object.
• SampleController.java
@RequestMapping("hello")
public String hello(RedirectAttributes redirectAttrs) { // (1)
redirectAttrs.addFlashAttribute("hello", "Hello World!"); // (2)
redirectAttrs.addFlashAttribute(new HelloBean("Bean Hello World!")); // (3)
return "redirect:/sample/hello?complete"; // (4)
}
• complete.jsp
(1)
(3) If first argument of addFlashAttribute method is omitted, the class name beginning with
lower case letter becomes the attribute name.
For example, the result of model.addFlashAttribute("helloBean", new
HelloBean()); is same as model.addFlashAttribute(new HelloBean());.
(4) Send a redirect request to another URL which will display the next screen instead of displaying
screen (View) directly.
(5) In the handler method after redirection, return view name of the screen that displays the data
added in (2) and (3).
(6) In the View (JSP) side, the data added to RedirectAttributes object can be obtained by
specifying “${attribute name}”.
For example, HTML escaping is performed using “${f:h(attribute name)}” function of EL
expression.
For the details of functions of EL expression that performs HTML escaping, refer to Cross Site
Scripting.
(7) The value stored in RedirectAttributes can be obtained from JavaBean by using
“${Attribute name.Property name}”.
Warning: The data cannot be passed to redirect destination even though it is added to Model.
Note: It is similar to the addAttribute method of Model. However survival time of data differs. In
addFlashAttribute of RedirectAttributes, the data is stored in a scope called flash scope. Data of
only 1 request (G in PRG pattern) can be referred after redirect. The data from the second request onwards is
deleted.
When request parameters are to be set dynamically to redirect destination, add the values to be passed to
RedirectAttributes object of argument.
@RequestMapping("hello")
public String hello(RedirectAttributes redirectAttrs) {
String id = "aaaa";
redirectAttrs.addAttribute("id", id); // (1)
// must not return "redirect:/sample/hello?complete&id=" + id;
return "redirect:/sample/hello?complete";
}
(1) Specify request parameter name in argument name and request parameter value
in argument ‘‘value and call addAttribute method of RedirectAttributes
object.
In the above example, it is redirected to "/sample/hello?complete&id=aaaa".
To insert values in redirect destination URL path dynamically, add the value to be inserted in
RedirectAttributes object of argument as shown in the example to set request parameters.
@RequestMapping("hello")
public String hello(RedirectAttributes redirectAttrs) {
String id = "aaaa";
redirectAttrs.addAttribute("id", id); // (1)
// must not return "redirect:/sample/hello/" + id + "?complete";
return "redirect:/sample/hello/{id}?complete"; // (2)
}
(1) Specify attribute name and the value using addAttribute method of
RedirectAttributes object.
(2) Specify the path of the variable “{Attribute name}” to be inserted in the redirect URL.
In the above example, it is redirected to "/sample/hello/aaaa?complete".
Add @CookieValue annotation to the argument of handler method to acquire the values from a cookie.
@RequestMapping("readCookie")
public String readCookie(@CookieValue("JSESSIONID") String sessionId, Model model) { // (1)
// do something
return "sample/readCookie"; // returns view name
}
(1) Specify name of the cookie in the value attribute of @CookieValue annotation.
In the above example, “JSESSIONID” value is passed from cookie to sessionId argument.
Note:
As in the case of @RequestParam , it has required attribute and defaultValue attribute. Also, the data type of the a
Refer to Retrieving request parameters individually for details.
To write values in cookie, call addCookie method of HttpServletResponse object directly and add the
value to cookie.
Since there is no way to write to cookie in Spring MVC (3.2.3 version), ** Only in this case,
HttpServletResponse can fetched as an argument of handler method of controller.**
@RequestMapping("writeCookie")
public String writeCookie(Model model,
HttpServletResponse response) { // (1)
Cookie cookie = new Cookie("foo", "hello world!");
response.addCookie(cookie); // (2)
// do something
return "sample/writeCookie";
}
(1)
Pagination related information is required for the requests performing list search.
Fetching org.springframework.data.domain.Pageable (henceforth called as Pageable) object
as an argument of handler method enables to handle pagination related information (page count, fetch record
count) easily.
Model object or RedirectAttributes object can be obtained as an argument of handler method and re-
sult message of business logic execution can be displayed by adding ResultMessages object to Model or
RedirectAttributes.
For return values of handler methods, various values can be fetched ; however, only the following values should
be used.
• HTML response
HTML response
To get HTML response to display the output of handler method, it has to return view name of JSP.
ViewResolver when generating HTML using JSP will be an inherited class of UrlBasedViewResolver
(InternalViewResolver and‘‘TilesViewResolver‘‘, etc).
An example using InternalViewResolver for JSP is given below; however, it is recommended to use
TilesViewResolver when the screen layout is in a templated format.
Refer to Screen Layout using Tiles for the usage of TilesViewResolver.
• spring-mvc.xml
Example of definition when using <mvc:view-resolvers> element added from Spring Framework
4.1
<mvc:view-resolvers>
<mvc:jsp prefix="/WEB-INF/views/" /> <!-- (5) -->
</mvc:view-resolvers>
• SampleController.java
@RequestMapping("hello")
public String hello() {
// omitted
return "sample/hello"; // (6)
}
(1)
Specify base directory (prefix of file path) where JSP files are stored.
By specifying prefix, there is no need to specify physical storage location of JSP files at the time
(2)
of returning View name in Controller.
Define InternalViewResolver for JSP using <mvc:jsp> element added from Spring
Framework 4.1.
(5)
• In prefix attribute, specify base directory (prefix of file path) where JSP file is stored.
• It need not be explicitly specified in suffix attribute as ".jsp" is used as default value.
Note: HTML output is generated using JSP in the above example, however, even if HTML is generated using
other template engine such as Velocity, FreeMarker, return value of handler method will be "sample/hello".
ViewResolver takes care of task to determine which template engine is to be used.
The solution to create a separate ViewResolver to resolve a view using its view name, however,
BeanNameViewResolver provided by Spring Framework is recommended.
Refer to File Download for the details of download processing.
• spring-mvc.xml
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="1" />
</bean>
Example of definition when using <mvc:view-resolvers> element added from Spring Framework
4.1
<mvc:view-resolvers>
<mvc:bean-name /> <!-- (3) -->
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
• SampleController.java
@RequestMapping("report")
public String report() {
// omitted
return "sample/report"; // (4)
}
• XxxExcelView.java
@Component("sample/report") // (5)
public class XxxExcelView extends AbstractExcelView { // (6)
@Override
protected void buildExcelDocument(Map<String, Object> model,
HSSFWorkbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception {
HSSFSheet sheet;
HSSFCell cell;
sheet = workbook.createSheet("Spring");
sheet.setDefaultColumnWidth(12);
// write a text at A1
cell = getCell(sheet, 0, 0);
setText(cell, "Spring-Excel test");
When View name "sample/report" is the return value of handler method, the data gener-
ated by View instance which is registered in step (5), is responded as download data.
(4)
Register View object as Bean by specifying View name to the name of component.
In above example, x.y.z.app.views.XxxExcelView instance is registered as a bean
(5)
with bean name (view name) as "sample/report" .
Refer to Domain Layer Implementation for the details of implementation of business logic.
Note: Controller should be used only for routing purposes (mapping requests to corresponding business logic)
and deciding the screen transition for each request as well as setting model data. Thereby, controller should be
simple as much as possible. By consistently following this policy, the contents of controller become clear which
ensures maintainability of controller even if the size of development is large.
Correlation check of input values should be done using Validation class which implements
org.springframework.validation.Validator interface.
Bean Validation can also be used for correlation check of input values.
Refer to Input Validation for the details of implementation of correlation check.
The implementation of correlation check itself should not be written in the handler method of controller.
However, it is necessary to add the Validator to
org.springframework.web.bind.WebDataBinder.
@Inject
PasswordEqualsValidator passwordEqualsValidator; // (1)
@InitBinder
protected void initBinder(WebDataBinder binder){
binder.addValidators(passwordEqualsValidator); // (2)
}
(1)
Execute business logic by injecting the Service in which business logic is implemented and calling the injected
Service method.
@Inject
SampleService sampleService; // (1)
@RequestMapping("hello")
public String hello(Model model){
String message = sampleService.hello(); // (2)
model.addAttribute("message", message);
return "sample/hello";
}
(2)
In this guideline, it is recommended to bind the data sent by HTML form to form object instead of the domain
object.
Therefore, the controller should perform the process of reflecting the values of form object to domain object
which is then passed to the method of service class.
@RequestMapping("hello")
public String hello(@Validated SampleForm form, BindingResult result, Model model){
// omitted
Sample sample = new Sample(); // (1)
sample.setField1(form.getField1());
sample.setField2(form.getField2());
sample.setField3(form.getField3());
// ...
// and more ...
// ...
String message = sampleService.hello(sample); // (2)
model.addAttribute("message", message); // (3)
return "sample/hello";
}
(1) Create domain object and reflect the values bound to form object in the domain object.
The process of reflecting values to domain object should be implemented by the handler method of controller.
However considering the readability of processing
method in case of large amount of code, it is recommended to delegate the process to Helper class.
Example of delegating the process to Helper class is shown below:
• SampleController.java
@Inject
SampleHelper sampleHelper; // (1)
@RequestMapping("hello")
public String hello(@Validated SampleForm form, BindingResult result){
// omitted
String message = sampleHelper.hello(form); // (2)
model.addAttribute("message", message);
return "sample/hello";
}
• SampleHelper.java
@Inject
SampleService sampleService;
(1)
Value is reflected to the domain object by calling the method of the injected Helper class. Dele-
gating the process to Helper class enables to keep the implementation of controller simple.
(2)
Call the Service class method to execute the business logic after creating domain object.
(3)
Note: Bean conversion functionality can be used as an alternative way to delegate the process of reflecting
form object values, to Helper class. Refer to Bean Mapping (Dozer) for the details of Bean conversion
functionality.
In this guideline, it is recommended that form object (and not domain object) must be used to for that data which
is to be bound to HTML form.
For this, it is necessary to reflect the values of domain object (returned by service layer) to form object. This
conversion should be performed in controller class.
@RequestMapping("hello")
public String hello(SampleForm form, BindingResult result, Model model){
// omitted
Sample sample = sampleService.getSample(form.getId()); // (1)
form.setField1(sample.getField1()); // (2)
form.setField2(sample.getField2());
form.setField3(sample.getField3());
// ...
// and more ...
// ...
model.addAttribute(sample); // (3)
return "sample/hello";
}
(1) Call the method of service class in which business logic is implemented and fetch domain
object.
(3) When there are fields only for display, add domain object to Model so that data can be referred.
Note: In JSP, it is recommended to refer the values from domain object instead of form object for the
fields to be only displayed on the screen.
The process of reflecting value to form object should be implemented by the handler method of controller. How-
ever considering the readability of handler method in case of large amount of code, it is recommended to delegate
the process to Helper class method.
• SampleController.java
@RequestMapping("hello")
public String hello(@Validated SampleForm form, BindingResult result){
// omitted
Sample sample = sampleService.getSample(form.getId());
sampleHelper.applyToForm(sample, form); // (1)
model.addAttribute(sample);
return "sample/hello";
}
• SampleHelper.java
(1) Call the method to reflect the values of domain object to form object.
Note: Bean conversion functionality can be used as an alternative method to delegate the process to Helper
class. Refer to Bean Mapping (Dozer) for the details of Bean conversion functionality.
Form object is the object (JavaBean) which represents HTML form and plays the following role.
1. Holds business data stored in the database so that it can be referred by HTML form (JSP).
2. Holds request parameters sent by HTML form so that they can be referred by handler method of
controller.
Create form object as a JavaBean. Spring Framework provides the functionality to convert and bind the request
parameters (string) sent by HTML form to the format defined in form object. Hence, the fields to be defined in
form object need not only be in java.lang.String format.
Tip: Regarding the mechanism provided by Spring Framework that performs format conversion
Spring Framework executes format conversion using the following 3 mechanisms and supports conversion
to basic format as standard. Refer to linked page for the details of each conversion function.
• java.beans.PropertyEditor implementations
Warning: In form object, it is recommended to maintain only the fields of HTML form and
not the fields which are just displayed on the screen. If display only fields are also maintained
in form object, more memory will get consumed at the time of storing form object in HTTP ses-
sion object causing memory exhaustion. In order to display the values of display only fields on the
screen, it is recommended to add objects of domain layer (such as Entity) to request scope by using
(Model.addAttribute).
Number format can be specified for each field using @NumberFormat annotation.
(1) Specify the number format of request parameter sent by HTML form. For example, binding of
value formatted by ”,” is possible since “”#, #”” format is specified as pattern.
When value of request parameter is “”1,050””, Integer object of “”1050”” will bind to the
property price of form object.
Date and time format for each field can be specified using @DateTimeFormat annotation.
pattern Specify Java date and time format. Refer to ‘Javadoc of SimpleDateFormat
2.
<http://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html>’_for
details.
style
3.
Specify style of date and time as two-digit string.
First digit will be style of date and second digit will be style of time.
Values that can be specified as style are given below.
Warning: @DateTimeFormat’s formatter is not strict in case of pattern attribute specified with
java.time.LocalDate on JSR-310 Date and Time API ("20150229" is invalid , but it will be regarded
as 28st February, 2015). Specifications are improved in Spring Framework 4.3 , but are affected because they
use Spring Framework 4.2 in TERASOLUNA Server Framework for JAVA (5.x). For details reference to
‘@DateTimeFormat’s JSR-310 formatter is not strict in case of pattern‘
@InitBinder // (1)
public void initWebDataBinder(WebDataBinder binder) {
binder.registerCustomEditor(
Long.class,
new CustomNumberEditor(Long.class, new DecimalFormat("#,#"), true)); // (2)
}
@InitBinder("sampleForm") // (3)
public void initSampleFormWebDataBinder(WebDataBinder binder) {
// ...
}
(1) If a method with @InitBinder annotation is provided, it is called before executing the
binding process and thereby default operations can be customized.
(2) For example, "#. #" format is specified for a field of type Long. This enables binding of
value formatted with ”,”.
(3) Default operation for each form object can be customized by specifying it in the value attribute
of @InitBinder annotation.
In the above example, the method is called before binding form object "sampleForm".
Since form object is validated using Bean Validation, it is necessary to specify the annotation which indicates
constraints of the field. Refer to Input Validation for the details of input validation.
Form object can also be called as form-backing bean and binding can be performed using @ModelAttribute
annotation. Initialize form-backing bean by the method having @ModelAttribute annotation. In this guide-
line, such methods are called as ModelAttribute methods and defined with method names like setUpXxxForm.
@ModelAttribute // (1)
public SampleForm setUpSampleForm() {
SampleForm form = new SampleForm();
// populate form
return form;
}
@ModelAttribute("xxx") // (2)
public SampleForm setUpSampleForm() {
SampleForm form = new SampleForm();
// populate form
return form;
}
@ModelAttribute
public SampleForm setUpSampleForm(
@CookieValue(value = "name", required = false) String name, // (3)
@CookieValue(value = "age", required = false) Integer age,
@CookieValue(value = "birthDate", required = false) Date birthDate) {
SampleForm form = new SampleForm();
form.setName(name);
form.setAge(age);
form.setBirthDate(birthDate);
return form;
}
(1) Class name beginning with lower case letter will become the attribute name to add to Model.
In the above example, "sampleForm" is the attribute name.
The returned object is added to Model and an appropriate process
model.addAttribute(form) is executed.
(2) When attribute name is to be specified to add to Model, specify it in the value attribute of
@ModelAttribute annotation. In the above example, "xxx" is the attribute name.
For returned object, appropriate process “model.addAttribute(“xxx”, form)”is executed and it is
returned to Model.
When attribute name other than default value is specified, it is necessary to specify
@ModelAttribute("xxx") at the time of specifying form object as an argument of
handler method.
(3) ModelAttribute method can pass the parameters required for initialization as with the case of
handler method. In the above example, value of cookie is specified using @CookieValue
annotation.
Note: When form object is to be initialized with default values, it should be done using ModelAttribute method.
In point (3) in above example , value is fetched from cookie, However, fixed value defined in constant class can
be set directly.
Note: Multiple ModelAttribute methods can be defined in the controller. Each method is executed before calling
handler method of controller.
Warning: If ModelAttribute method is executed for each request, initialization needs to be repeated for each
request and unnecessary objects will get created. So, for form objects which are required only for specific
requests, should be created inside handler method of controller and not through the use of ModelAttribute
method.
It is possible to bind form object added to the Model to HTML form(JSP) using <form:xxx> tag.
For the details of <form:xxx> tag, refer to Using Spring’s form tag library.
<form:form modelAttribute="sampleForm"
action="${pageContext.request.contextPath}/sample/hello"> <!-- (2) -->
Id : <form:input path="id" /><form:errors path="id" /><br /> <!-- (3) -->
Name : <form:input path="name" /><form:errors path="name" /><br />
Age : <form:input path="age" /><form:errors path="age" /><br />
Gender : <form:input path="genderCode" /><form:errors path="genderCode" /><br />
Birth Date : <form:input path="birthDate" /><form:errors path="birthDate" /><br />
</form:form>
(1)
(3)
It is possible to bind the request parameters sent by HTML form to form object and pass it as an argument to the
handler method of controller.
@RequestMapping("hello")
public String hello(
@Validated SampleForm form, // (1)
BindingResult result,
Model model) {
if (result.hasErrors()) {
return "sample/input";
}
// process form...
return "sample/hello";
}
@ModelAttribute("xxx")
public SampleForm setUpSampleForm() {
SampleForm form = new SampleForm();
// populate form
return form;
}
@RequestMapping("hello")
public String hello(
@ModelAttribute("xxx") @Validated SampleForm form, // (2)
BindingResult result,
Model model) {
// ...
}
(1) Form object is passed as an argument to the handler method of controller after reflecting
request parameters to the form object.
(2) When the attribute name is specified in ModelAttribute method, it is necessary to explicitly
specify attribute name of form object as @ModelAttribute("xxx").
Warning: When attribute name specified by ModelAttribute method and attribute name specified in the
@ModelAttribute("xxx") in the argument of handler method are different, it should be noted that a
new instance is created other than the instance created by ModelAttribute method. When attribute name is not
specified with @ModelAttribute in the argument to handler method, the attribute name is deduced as the
class name with first letter in lower case.
Error (including input validation error) that occurs while binding request parameter sent by HTML form to form
object, is stored in org.springframework.validation.BindingResult.
@RequestMapping("hello")
public String hello(
@Validated SampleForm form,
BindingResult result, // (1)
Model model) {
if (result.hasErrors()) { // (2)
return "sample/input";
}
// ...
}
It is also possible to determine field errors, global errors (correlated check errors at class level) separately. These
can be used separately if required.
Implementing JSP
Implement View using JSP to generate response(HTML) as per the requirement of the client.
Use the class provided by Spring Framework as the ViewResolver for calling JSP. Refer to HTML response
for settings of ViewResolver.
• Displaying codelist
In this chapter, usage of main JSP tag libraries are described. However, refer to respective documents for the
detailed usage since all JSP tag libraries are not described here.
JSTL
3. • http://download.oracle.com/otndocs/jcp/jstl-1.2-mrel2-
eval-oth-JSpec/
Create a JSP that contains directive declaration which are required by all the JSP files of the project. By specifying
this JSP in <jsp-config>/<jsp-property-group>/<include-prelude> element of web.xml,
eliminates the need to declare these directives and each and every JSP file of the project. Further, this file is
provided in blank project also.
• include.jsp
• web.xml
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<include-prelude>/WEB-INF/views/common/include.jsp</include-prelude> <!-- (4) -->
</jsp-property-group>
</jsp-config>
(1) JSP tag libraries of JSTL are declared. In this example, core and fmt are used.
(2) JSP tag libraries of Spring Framework are declared. In this example, spring, form and sec
are used.
To display the value stored in Model (form object or domain object) in HTML, use EL expressions or JSP tag
libraries provided by JSTL.
• SampleController.java
@RequestMapping("hello")
public String hello(Model model) {
model.addAttribute(new HelloBean("Bean Hello World!")); // (1)
return "sample/hello"; // returns view name
}
• hello.jsp
(2) In View(JSP), data added to the Model object can be retrieved by describing ${Attribute
name.Property name of JavaBean}.
In this example, HTML escaping is performed using ${f:h(Attribute
name.Property name of JavaBean)} function of EL expression.
Note: Since HTML escaping function (f:h) is provided in the common components, always use it if
EL expressions are used to output values in HTML. For details of function of EL expression that perform
HTML escaping, refer to Cross Site Scripting.
(1) Specify the values fetched using EL expressions in value attribute of <c:out> tag. HTML
escaping is also performed.
Note: Refer to CHAPTER 4 General-Purpose Actions of JavaServer Pages Standard Tag Li-
brary(Version 1.2)for the details of <c:out>.
Note: Refer to CHAPTER 9 Formatting Actions of JavaServer Pages Standard Tag Library(Version
1.2)for the details of <fmt:formatNumber>.
Use JSP tag library provided by JSTL to output format date and time value.
(1) Specify the value fetched using EL expression in value attribute of <fmt:formatDate>
tag. Specify the format to be displayed in pattern attribute. In this example,
“yyyy-MM-dd” is specified.
When value received for ${helloBean.dateItem} is 2013-3-2, “2013-03-02” is
displayed on the screen.
Note: Refer to CHAPTER 9 Formatting Actions of JavaServer Pages Standard Tag Library(Version 1.2)
for details of <fmt:formatDate>.
Note: JSP tag library provided by Joda Time should be used to use org.joda.time.DateTime as date and
time object type. Refer to Date Operations (Joda Time) for the details of Joda Time.
When a request URL (a URL for calling Controller method) is to be set for action attribute of <form> el-
ement(<form:form> element of JSP tag library) of HTML and href attribute of <a> element, a URL is
generated using either of the methods described below.
• Build a request URL using EL function which has been added from Spring Framework 4.1
Note: Although either of these methods can be used, using both the methods together in a single application
should be avoided since it may result in decrease in maintainability.
An implementation sample of the Controller method used further in the description is shown.
In the description hereafter, an implementation method is described wherein a request URL is generated for
calling the method given below.
package com.example.app.hello;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("hello")
@Controller
public class HelloController {
// (1)
@RequestMapping({"", "/"})
public String hello() {
return "hello/home";
}
(1)
First, the method to build a request URL using a character string is explained.
Tip:
are available as JSP tag libraries which build URL. A request URL can also be built using these JSP tag
libraries.
When it is necessary to build a request URL dynamically, a URL is preferably built by using these JSP tag
libraries.
Building a request URL by using EL function added from Spring Framework 4.1
Next, a method to build a request URL using EL function (spring:mvcUrl) added from Spring Framework
4.1 is described.
If spring:mvcUrl function is used, a request URL can be built by linking with meta information of Controller
method (method signature and annotation etc).
Request mapping name must not be duplicated. When the name is duplicated, a unique name must be
specified in name attribute of @RequestMapping annotation.
When a request mapping name assigned to Controller method is to be verified, settings given below should
be added to logback.xml.
<logger name="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandler
<level value="trace" />
</logger>
If the program is rebooted after applying the settings given above, the log shown below is output.
Use JSP tag library provided by Spring Framework to bind form object to HTML form and to display the values
stored in form object.
<form:form action="${pageContext.request.contextPath}/sample/hello"
modelAttribute="sampleForm"> <%-- (1) --%>
Id : <form:input path="id" /> <%-- (2) --%>
</form:form>
Specify name of property to bind in the path attribute of <form:xxx> tag. xxx part changes
along with each input element.
(2)
Note: For the details of <form:form> , <form:xxx> tag refer to Using Spring’s form tag library.
To display the contents of input validation error, use JSP tag library provided by Spring Framework.
<form:form action="${pageContext.request.contextPath}/sample/hello"
modelAttribute="sampleForm">
Id : <form:input path="id" /><form:errors path="id" /><%-- (1) --%>
</form:form>
(1) Specify name of the property to display the error in path attribute of <form:errors> tag.
To display the message notifying the output of processing the request, use JSP tag library provided in common
components.
<div class="messages">
<h2>Message pattern</h2>
<t:messagesPanel /> <%-- (1) --%>
</div>
Displaying codelist
To display the codelist (provided in common components), use JSP tag library provided by Spring Framework.
Codelist can be referred from JSP in the same way as java.util.Map interface.
Refer to Codelist for details.
<form:select path="orderStatus">
<form:option value="" label="--Select--" />
<form:options items="${CL_ORDERSTATUS}" /> <%-- (1) --%>
</form:select>
Label part is displayed on the screen for the value selected in select box.
Strings for screen name, element name and guidance etc can be directly written in JSP when internationalization
is not required.
However, when internationalization is required, display the values acquired from property file using JSP tag
library provided by Spring Framework.
• properties
# (1)
label.orderStatus=Order status
• jsp
(1)
Note: The value specified in text attribute is displayed when property value could not be acquired.
When display is to be switched according to some value in model, use JSP tag library provided by JSTL.
Switch display using <c:if> tag or <c:choose> provided by JSP tag library of JSTL.
<c:choose>
<c:when test="${customer.type == 'premium'}"> <%-- (1) --%>
<%-- ... --%>
</c:when>
<c:when test="${customer.type == 'general'}">
<%-- ... --%>
</c:when>
<c:otherwise> <%-- (2) --%>
<%-- ... --%>
</c:otherwise>
</c:choose>
When result of test attribute of all <c:when> tags is false, <c:otherwise> tag is eval-
uated.
(2)
Note: Refer to CHAPTER 5 Conditional Actions of JavaServer Pages Standard Tag Library(Version
1.2)for details.
To repeat display of collection stored in model, use JSP tag library provided by JSTL.
Repeated display can be done using <c:forEach> provided by JSP tag library of JSTL.
<table>
<tr>
<th>No</th>
<th>Name</th>
</tr>
<c:forEach var="customer" items="${customers}" varStatus="status"> <%-- (1) --%>
<tr>
<td>${status.count}</td> <%-- (2) --%>
<td>${f:h(customer.name)}</td> <%-- (3) --%>
</tr>
</c:forEach>
</table>
This is the value acquired from the object stored in variable specified by var attribute of
<c:forEach> tag.
(3)
Note: Refer to CHAPTER 6 Iterator Actions of JavaServer Pages Standard Tag Library(Version 1.2)for
details.
To display links of pagination on the screen while displaying the list, use JSP tag library provided in common
components.
Display the link for pagination using <t:pagination> provided in common components. Refer to Pagination
for details.
To switch display according to authority of the user who has logged in, use JSP tag library provided by Spring
Security.
Switch display using <sec:authorize> provided by Spring Security. Refer to Authorization for details.
Implementing JavaScript
When it is necessary to control screen items (controls of hide/display, activate/deactivate, etc.) after screen ren-
dering, control items using JavaScript.
Todo
TBD
It is recommended to specify attribute values related to screen design in style sheet (css file) and not in
JSP(HTML) directly.
In JSP(HTML), specify id attribute to identify the items uniquely and class attribute which indicates
classification of elements.
While, specify values related to actual location of elements or appearance should be specified in style sheet (css
file).
By configuring in this way, it is possible to reduce the design related aspects from the implementation of JSP.
Simultaneously, if there is any change in design, only style sheet (css file) is modified and not JSP.
Note: When form is created using <form:xxx>, id attribute will be set automatically. Application developer
should specify class attribute.
Here, common logic indicates processes which are required to be executed before and after execution the con-
troller.
Common processes independent of Spring MVC are implemented using Servlet Filter.
However, when common processes are to be executed only for the certain requests mapped with handler method
of controller,
then it should be implemented using Handler Interceptor and not Servlet Filter.
• java
• web.xml
(2)
(3)
• web.xml
<filter>
<filter-name>clientInfoPutFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- (1
</filter>
<filter-mapping>
<filter-name>clientInfoPutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
• applicationContext.xml
Add the Servlet Filter class to Bean definition file (applicationContext.xml). At this
time, id attribute of bean definition should be assigned with the filter name (value specified in
(2)
<filter-name> tag) specified in web.xml.
Implementing HandlerInterceptor
HandlerInterceptor can execute the process keeping in mind the following 3 points.
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method m = handlerMethod.getMethod();
logger.info("[SUCCESS CONTROLLER] {}.{}", new Object[] {
m.getDeclaringClass().getSimpleName(), m.getName()});
}
• spring-mvc.xml
<mvc:interceptors>
<!-- ... -->
<mvc:interceptor>
<mvc:mapping path="/**" /> <!-- (2) -->
(2)
Specify the pattern of path, where the created HandlerInterceptor need not be applied.
(3)
(4)
Here, common process indicates the process that should be commonly implemented in all the controllers.
Implementing HandlerMethodArgumentResolver
When an object that is not supported by default in Spring Framework is to be passed as controller argument,
HandlerMethodArgumentResolver is to be implemented in order to enable controller to be able to receive the
argument.
• JavaBean
// ....
• HandlerMethodArgumentResolver
@Override
public boolean supportsParameter(MethodParameter parameter) {
return CommonParameters.class.equals(parameter.getParameterType()); // (3)
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
CommonParameters params = new CommonParameters(); // (4)
params.setParam1(webRequest.getParameter("param1"));
params.setParam2(webRequest.getParameter("param2"));
params.setParam3(webRequest.getParameter("param3"));
return params;
}
• Controller
@RequestMapping(value = "home")
public String home(CommonParameters commonParams) { // (5)
logger.debug("param1 : {}",commonParams.getParam1());
logger.debug("param2 : {}",commonParams.getParam2());
logger.debug("param3 : {}",commonParams.getParam3());
// ...
return "sample/home";
• spring-mvc.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- ... -->
<bean class="x.y.z.CommonParametersMethodArgumentResolver" /> <!-- (6) -->
<!-- ... -->
</mvc:argument-resolvers>
</mvc:annotation-driven>
(1)
Implement org.springframework.web.method.support.HandlerMethodArgumentResolv
interface.
(2)
Determine parameter type. For example, when type of JavaBean that retains common parameters
is specified as argument of controller, resolveArgument method of this class is called.
(3)
Fetch the values of request parameters, set the parameters and return the JavaBean that retains
the value of common parameters.
(4)
(5) Specify JavaBean that retains common parameters in the argument of handler method of
controller.
Object returned in step (4) is passed.
Note: When parameters are to be passed commonly to handler methods of all controllers, it is effective to convert
the parameters to JavaBean using HandlerMethodArgumentResolver. Parameters referred here are not restricted
to request parameters.
Implementing “@ControllerAdvice”
In a class with @ControllerAdvice annotation, implement common processes which are to be executed in
multiple Controllers.
Tip: @ControllerAdvice annotation is a mechanism added from Spring Framework 3.2; however, since the
processing was applied to all Controllers, it could only implement common processes of entire application.
From Spring Framework 4.0, it has been improved in such a way that controller can be specified flexibly for
applying common processes. With this improvement, it is possible to implement a common process in various
granularities.
Methods to specify Controller (methods to specify attributes) for applying common processes are described below.
Tip: basePackageClasses attribute / basePackages attribute / value attribute are the at-
tributes to specify base package that stores the Controller for applying common processes. However, when
basePackageClasses attribute is used,
@ControllerAdvice // (1)
@Order(0) // (2)
public class SampleControllerAdvice {
// (3)
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class,
new CustomDateEditor(dateFormat, true));
}
Specify priority for common processes by assigning the @Order annotation. It should be speci-
fied when multiple ControllerAdvice have ordering for example there are dependencies between
(2)
them. Otherwise it is not necessary.
(3)
// (1)
@ExceptionHandler(PessimisticLockingFailureException.class)
public String handlePessimisticLockingFailureException(
PessimisticLockingFailureException e) {
return "error/lockError";
}
• ControllerAdvice
// (1)
@ModelAttribute
public CommonParameters setUpCommonParameters(
@RequestParam(value = "param1", defaultValue="def1") String param1,
@RequestParam(value = "param2", defaultValue="def2") String param2,
@RequestParam(value = "param3", defaultValue="def3") String param3) {
CommonParameters params = new CommonParameters();
params.setParam1(param1);
params.setParam2(param2);
params.setParam3(param3);
return params;
}
• Controller
@RequestMapping(value = "home")
public String home(@ModelAttribute CommonParameters commonParams) { // (2)
logger.debug("param1 : {}",commonParams.getParam1());
logger.debug("param2 : {}",commonParams.getParam2());
logger.debug("param3 : {}",commonParams.getParam3());
// ...
return "sample/home";
}
(2)
Measures should be taken to prevent double submission as the same process gets executed multiple times by
clicking Send button multiple times or refreshing (refresh using F5 button) the Finish screen.
For the problems occurring when countermeasures are not taken and details of implementation method, refer to
Double Submit Protection .
In the default operations of Spring MVC, model (form object, domain object etc.) is not stored in session.
When it is to be stored in session, it is necessary to assign @SessionAttributes annotation to the controller
class.
When input forms are split on multiple screens, usage of @SessionAttributes annotation should be studied
since model (form object, domain object etc.)
can be shared between multiple requests for executing a series of screen transitions.
However, whether to use @SessionAttributes annotation should be determined after confirming the
warning signs of using the session.
For details of session usage policy and implementation at the time of session usage, refer to Session Management
.
The method to create a war file to be deployed on application server and a jar file of env module (module to store
the file environment dependent file) is described below.
In case of a project created using Maven Archetype, the following 2 methods are provided as methods to create a
war file.
• Build method wherein jar file of env module is not included in war file (recommended)
• Build method wherein jar file of env module is included in war file
This guideline recommends Build method wherein jar file of env module is not included in war file. Other build
method apart from those mentioned here can also be used.
However, the war file and jar file to be released in test environment and production environment should not
be created using the functionality provided by IDE such as Eclipse. In some of the IDE functionalities like
Eclipse, class files are created using an independent compiler which has been optimized for development, hence
there could be a risk of unexpected error during the application execution due to difference in the compiler.
1. Make sure to have a multi-project structure. 2.As far as possible, consolidate the configuration files (ex. log-
back.xml, jdbc.properties) having environment dependency in one project. Hereafter, this project is expressed
as *-env.
• ex. terasoluna-tourreservation-env
3. Projects other than *-env never have a setting for environment dependency.
• However, it is allowed to store the environment dependency configuration files for test under
src/test/resources.
• Deploy not only *.jar files but also *.war files as work products in package repository. Consequently, these
jar/war files should not include environment dependencies.
• Store the configuration values required for operations on the developer’s PC in the file under
src/main/resources, as default values.
• Store the configuration files that differ with each environment such as test server, production server, in the
folder other than src/main/resources (ex. configs/test-server). Then, use “profile” function of Maven that
automatically replaces the configuration values depending on environment in order to build *-env-x.y.z.jar
file.
The above structure facilitates proper development in all the scenarios of software development lifecycle.
1. In local development environment, check out both main project and *-env project and include env project
in build path of the main project so as to do coding and testing in the local development environment.
2. On CI server, execute the test and perform packaging using a build tool (Maven) and deploy the artifact in
package repository whenever required.
3. Application can work on test server and production server by adding *-env project built as per the applica-
tion release environment, to the main project which is stored in package repository.
When build is done using Maven, confirm whether home directory of JDK which is used during compilation in
the environment variable JAVA_HOME, has been specified.
If the environment variable is not set or the home directory of JDK having different version has been specified, an
appropriate home directory should be specified in environment variable.
echo %JAVA_HOME%
set JAVA_HOME={Please set home directory of JDK}
echo $JAVA_HOME
JAVA_HOME={Please set home directory of JDK}
Note: It is advisable to set the environment variable JAVA_HOME in the user environment variable of OS user
wherein build is to be done.
Build method wherein jar file of env module is not included in war file
cd C:\work\todo
Specify warpack in Maven profile (-P parameter) and run Maven install.
If the Maven package is run successfully, a war file that does not include jar file of env module is created in the
target directory of web module.
(Example: C:\work\todo\todo-web\target\todo-web.war)
In the above example, install is specified in goal and war file is installed in local repository, however it is
advisable to specify
cd C:\work\todo\todo-env
Specify Profile ID to identify environment in Maven profile (-P parameter) and run Maven package.
If Maven package is run successfully, jar file for the specified environment is created in target directory of env
module.
(Example:
C:\work\todo\todo-env\target\todo-env-1.0.0-SNAPSHOT-test-server.jar)
In case of a project created using Maven Archetype, following profile IDs are defined by default.
• local: Profile for local environment of the developer (for IDE development environment) (default profile)
The above 3 profiles are provided by default; however you can add or modify them as per the environment config-
uration of the system to be developed.
Build method wherein jar file of env module is included in war file
Warning: Points to be noted when including a jar file of env module in war file
When jar file of env module is included in war file, the war file cannot be deployed in other environment;
hence war file should be managed so that it is not deployed to other environment (especially in production
environment) by mistake.
Moreover, when using a method in which war file is created for each environment and released in each en-
vironment, it should be noted that war file released in production environment can never be the war file for
which testing is complete. This is for the re-compilation at the time of creating war file for the production en-
vironment. When creating the war file and releasing the same for each environment, it is especially important
to use the VCS (Version Control System) functionality (Tag functionality etc.) like Git or Subversion and to
establish a mechanism to create a war file which is to be released in production environment and various test
environments, through the use of tested source files.
cd C:\work\todo
In Maven profile (-P parameter), specify Profile ID to identify environmentdefined in env module and
warpack-with-env, and then run the Maven package.
If Maven package is run successfully, war file which includes jar file of env module is created in target directory
of web module.
(Example: C:\work\todo\todo-web\target\todo-web.war)
Deploy
Deploy on Tomcat
Deployment method (procedure) when Tomcat is used as an application server is given below.
1. Specify the profile of Maven as per the AP server environment in which the application is to be released
and build *-env project.
2. Place *-env-x.y.z.jar file built above in the folder of AP server decided in advance. ex. /etc/foo/bar/abcd-
env-x.y.z.jar
4. If Tomcat 7 is used, add /etc/foo/bar/*.jar into class path using VirtualWebappLoader function of the Tom-
cat.
<Loader className="org.apache.catalina.loader.VirtualWebappLoader"
virtualClasspath="/etc/foo/bar/*.jar" />
5. If Tomcat 8 is used, add /etc/foo/bar/*.jar into class path using Resource function of the Tomcat.
<Resources className="org.apache.catalina.webresources.StandardRoot">
<PreResources className="org.apache.catalina.webresources.DirResourceSet"
base="/etc/foo/bar/"
internalPath="/"
webAppMount="/WEB-INF/lib" />
</Resources>
Note:
• When autoDeploy is disabled, Web application does not start by just placing the war file in
[CATALINA_HOME]/webapps. war file should always be unjarred (unzipped).
When releasing the Web application on application servers (Example: WebSphere, WebLogic, JBoss) where
a mechanism for adding a class path for each web application (which is provided in VirtualWebappLoader of
Tomcat) is not provided, the method to release it after adding *-env-x.y.z.jar file under WEB-INF/lib of war file is
the easiest.
1. Specify profile of Maven as per the AP server environment in which application is to be released and build
*-env project.
2. Copy *.war file deployed in the package repository to the working directory.
3. Add it under WEB-INF/lib of war file using add option of jar command as follows.
Note: For a method to deploy a war file on application server, refer to the manual of application server to be used.
Here, a method to embed the jar file of env module in war file using jar command is given.
cd C:\work\todo\todo-env
mvn org.apache.maven.plugins:maven-dependency-plugin:2.5:get^
-DgroupId=com.example.todo^
-DartifactId=todo-web^
-Dversion=1.0.0-SNAPSHOT^
-Dpackaging=war^
-Ddest=target/todo-web.war
If the command is run successfully, the specified war file is copied to the target directory of env module.
(Example: C:\work\todo\todo-env\target\todo-web.war)
Note:
Copy the created jar file to working directory (target\WEB-INF\lib) once and add it to the war file.
mkdir target\WEB-INF\lib
copy target\todo-env-1.0.0-SNAPSHOT-test-server.jar target\WEB-INF\lib\.
cd target
jar -uvf todo-web.war WEB-INF\lib
mkdir -p target/WEB-INF/lib
cp target/todo-env-1.0.0-SNAPSHOT-test-server.jar target/WEB-INF/lib/.
cd target
jar -uvf todo-web.war WEB-INF/lib
The problem when jar command is not found can be resolved using either of the following measures.
• Specify the jar command with full path. In case of Windows, %JAVA_HOME%\bin\jar and in case of
Linux, ${JAVA_HOME}/bin/jar can be specified.
Continuous deployment
Continuous deployment is constantly releasing the target software through continuous looping of project (source
code tree) structure, version control, inspection, build operations and lifecycle management.
During development, release the software of SNAPSHOT version in the package repository and development
AP server and execute the test. To release the software officially, tagging to source code tree in VCS needs be
performed after assigning a version number. In this way, the flow of build and deployment slightly differs in the
snapshot release and official release.
To deploy the application on AP server that provides Web service, irrespective of snapshot version or official
release version, a group of environment dependency configuration files and *.war file should be deployed in a set
as per the target release AP server environment.
Separating the operation of registering libraries (jar, war) without environment dependency settings, in Maven
repository and the operation of actually deploying them on AP server facilitates deployment.
Please note that there are 2 types of repositories in Maven package repository i.e. snapshot repository and release
repository with a few limitations.
• Software of SNAPSHOT version cannot be registered in release repository. release repository also cannot
be registered in snapshot repository.
• In release repository, artifact having the same GAV information can be registered only once.
(GAV=groupId, artifactId, version)
• In snapshot repository, artifact having the same GAV information can be re-registered many times.
Operations of SNAPSHOT version A simple delivery flow of SNAPSHOT version software is as shown in
the following figure.
• In case of compilation error, certain violations of code metrics or in case the test fails, the subsequent
operations should be stopped.
3. Upload (mvn deploy) the artifact (jar, war file) on Maven package repository server. |
Operations of RELEASE version In case of official release, since it is necessary to assign the version number,
the flow becomes slightly more complex than the SNAPSHOT release.
2. Check out the source code from development trunk (or release branch).
• In case of compilation error, certain violations of code metrics or in case the test fails, the subsequent
operations should be stopped.
6. Upload (mvn deploy) the artifact (jar, war file) on Maven package repository server.
Todo
Here, should the version tag of pom.xml of trunk source tree be written at the end till it is replaced by the next
SNAPSHOT version and committed?
Release on Application Server To release the application on AP server that provides Web service, release the
*.war file registered in Maven package repository and the group of environment dependency configuration files in
a set according to the target release AP server environment. This has same flow irrespective of snapshot release or
official release.
1. Download war file of the version to be released from Maven package repository.
2. Check out *-resources project (that consolidates environment dependency configuration files) from VCS.
3. Using “profile” function of Maven, replace the contents with group of configuration files according to the
target release environment, package the resources project and create *-resources-x.y.z.jar.
4. Add the created *-resources-x.y.z.jar file under WEB-INF/lib folder of war file.
• In case of Tomcat, instead of adding *-resources-x.y.z.jar to war file, copy it to any path of Tomcat server
and specify that path in the extended class path of VirtualWebappLoader.
Note: War file can be downloaded from Maven package repository with “get goal” of maven-dependency-plugin.
mvn org.apache.maven.plugins:maven-dependency-plugin:2.5:get \
-DgroupId=com.example \
-DartifactId=mywebapp \
-Dversion=0.0.1-SNAPSHOT \
-Dpackaging=war \
-Ddest=${WORKSPACE}/target/mywebapp.war
Package of environment dependency configuration files can be added to mywebapp.war file using the following
commands.
mkdir -p $WORKSPACE/target/WEB-INF/lib
cd $WORKSPACE/target
cp ./mywebapp-resources*.jar WEB-INF/lib
jar -ufv mywebapp.war WEB-INF/lib
4.1.1 Overview
It is mandatory to check whether the value entered by the user is correct. Validation of input value is broadly
classified into
1. Validation to determine whether the input value is valid by just looking at it irrespective of the context such
as size and format.
2. Validation of whether the changes in input value are valid depending on the system status.
1. is an example of mandatory check and number of digits check and 2. is an example of check of whether EMail
is registered and whether order count is within the count of the available stock.
In this section, 1. is explained and this check is called “Input validation”. 2. is called “Business logic check”. For
business logic check, refer to Domain Layer Implementation.
In this guideline, validation check should be performed in application layer whereas business logic check should
be performed in domain layer.
Input validation of Web application is performed at server side and client side (JavaScript). It is mandatory to
check at the Server Side. However, if the same check is performed at the client side also, usability improves since
validation results can be analyzed without communicating with the server.
Warning: Input validation should be performed at the server side as the process at client side may be altered
using JavaScript. If the validation is performed only at the client side without performing at the server side,
the system may be exposed to danger.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Todo
Input validation at client side will be explained later. Only input validation at the server side is mentioned in the
first version.
Input validation is classified into single item check and correlation item check.
Correlation item
check
Check comparing multiple fields Password and confirm Bean Validation or Validation
password check class implementing
org.springframework.validation.Validator
interface
Spring supports Bean Validation which is a Java standard. This Bean Validation is used for single item check.
Bean Validation or org.springframework.validation.Validator interface provided by Spring is
used for correlation item check.
When Bean validation 1.1 (Hibernate Validator 5.x) or higher version is to be used, in addition to jar file of
Hibernate Validator and jar file storing API specifications class (javax.validation package class) of Bean
Validation, libraries that store the following classes are required.
• API specifications class of Expression Language 2.2 or higher version (javax.el package class)
If the file is run by deploying on application server, dependent libraries need not be added; since these libraries
are provided by application server. However, when it is run in standalone environment (JUnit etc.), these libraries
need to be added as dependent libraries.
An example of adding libraries which are required when running Bean Validation 1.1 or higher version in stan-
dalone environment is given below.
(2)
Note: In the above example of settings, it is a prerequisite that version of dependent libraries should be stored in
the parent project. Therefore, <version> element is not specified.
Note: <mvc:annotation-driven> settings are carried out in spring-mvc.xml, Bean Validation is enabled.
Implementation method is explained using “New user registration” process as an example. Rules for checking
“New user registration” form are provided below.
• Form class
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
@NotNull // (1)
@Size(min = 1, max = 20) // (2)
private String name;
@NotNull
@Size(min = 1, max = 50)
@Email // (3)
private String email;
@NotNull // (4)
@Min(0) // (5)
@Max(200) // (6)
private Integer age;
// omitted setter/getter
}
In Spring MVC, when form is sent with input fields left blank,
empty string instead of null bindsto form object by default.
This @NotNull checks that name exists as request parameter.
Since empty string binds to the field where string is left blank by default in Spring MVC,
‘1 or more character’ rule indicates Mandatory input.
(4) When form is sent without entering any number in input field, null binds to form object so
@NotNull indicates mandatory input of age.
Tip: Refer to Bean Validation check rules and Hibernate Validator check rules for standard annotations of
Tip: Refer to Binding null to blank string field for the method of binding null when input field is left
blank.
• Controller class
package com.example.sample.app.validation;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("user")
public class UserController {
@ModelAttribute
public UserForm setupForm() {
return new UserForm();
}
(4) Use BindingResult.hasErrors() method to determine the check result of step (2).
When the result of hasErrors() is true, return to form display screen as there is an error
in input value.
(5) Input validation should be executed againeven at the time of submission on the confirmation
screen.
There is a possibility of data tempering and hence Input validation must be performed just
before entering business logic.
Note: @Validated is not a standard Bean Validation annotation. It is an independent annotation pro-
vided by Spring. Bean Validation standard javax.validation.Valid annotation can also be used.
However, @Validated is better as compared to @Valid annotation. Validation group can be specified
in case of @Validated and hence @Validated is recommended in this guideline.
• JSP
<!DOCTYPE html>
<html>
<%-- WEB-INF/views/user/createForm.jsp --%>
<body>
<form:form modelAttribute="userForm" method="post"
action="${pageContext.request.contextPath}/user/create">
<form:label path="name">Name:</form:label>
<form:input path="name" />
<form:errors path="name" /><%--(1) --%>
<br>
<form:label path="email">Email:</form:label>
<form:input path="email" />
<form:errors path="email" />
<br>
<form:label path="age">Age:</form:label>
<form:input path="age" />
<form:errors path="age" />
<br>
<form:button name="confirm">Confirm</form:button>
</form:form>
</body>
</html>
Error message is displayed as follows if this form is sent with all the input fields left blank.
Error messages state that Name and Email are blank and Age is null.
Note: In Bean Validation, null is a valid input value except the following annotations.
• javax.validation.constraints.NotNull
• org.hibernate.validator.constraints.NotEmpty
• org.hibernate.validator.constraints.NotBlank
In the above example, error messages related to @Min and @Max annotations are not displayed. This is because
null is a valid value for @Min and @Max annotations.
Error message is not displayed since input value of Name fulfills validation conditions.
Error message is displayed since input value of Email is not in Email format though it fulfills the conditions
related to string length.
Error message is displayed since input value of Age exceeds maximum value.
Change the form as follows to change the style at the time of error.
<form:button name="confirm">Confirm</form:button>
</form:form>
(1) Specify class name for <label> tag in cssErrorClass attribute at the time of error.
(2) Specify class name for <input> tag in cssErrorClass attribute at the time of error.
For example, if the following CSS is applied to this JSP, error screen is displayed as follows.
.form-horizontal input {
display: block;
float: left;
}
.form-horizontal label {
display: block;
float: left;
text-align: right;
float: left;
}
.form-horizontal br {
clear: left;
}
.error-label {
color: #b94a48;
}
.error-input {
border-color: #b94a48;
margin-left: 5px;
}
.error-messages {
color: #b94a48;
display: block;
padding-left: 5px;
overflow-x: auto;
}
Instead of displaying the error messages next to each input field, output them collectively.
An example of error message is shown when the following CSS class is applied.
.form-horizontal input {
display: block;
float: left;
}
.form-horizontal label {
display: block;
float: left;
text-align: right;
float: left;
}
.form-horizontal br {
clear: left;
}
.error-label {
color: #b94a48;
}
.error-input {
border-color: #b94a48;
margin-left: 5px;
}
.error-message-list {
color: #b94a48;
padding:5px 10px;
background-color: #fde9f3;
border:1px solid #c98186;
border-radius:5px;
margin-bottom: 10px;
}
By default, field name is not included in error message, hence it is difficult to understand which error message
corresponds to which field.
Therefore, when an error message is to be displayed in a list, it is necessary to define the message such that field
name is included in the error message.
For method of defining error messages, refer to “Definition of error messages”.
Error messages are output in a random order and the output order cannot be controlled by standard function.
Therefore, when an output order needs to be controlled (to be kept constant), an extended implementation such as
are required. Therefore, the cost is higher as compared to “displaying error messages next to input field”. This
guideline recommends the method of “displaying error messages next to input field” when there are no
constraints due to screen requirements.
A mechanism of @GroupSequence annotation is provided to control the check sequence; however, add a note that
this mechanism is not to control the output order of error message as operations given below are performed.
• If multiple errors (errors in multiple fields) occur in the check of identical groups, then the output order of
error messages would be random.
Note: Use <spring:nestedPath> tag to display error messages collectively outside the <form:form>
tag.
<spring:nestedPath path="userForm">
<form:errors path="*" element="div"
cssClass="error-message-list" />
</spring:nestedPath>
<hr>
<form:form modelAttribute="userForm" method="post"
action="${pageContext.request.contextPath}/user/create">
<form:label path="name" cssErrorClass="error-label">Name:</form:label>
<form:input path="name" cssErrorClass="error-input" />
<br>
<form:label path="email" cssErrorClass="error-label">Email:</form:label>
<form:input path="email" cssErrorClass="error-input" />
<br>
<form:label path="age" cssErrorClass="error-label">Age:</form:label>
<form:input path="age" cssErrorClass="error-input" />
<br>
<form:button name="confirm">Confirm</form:button>
</form:form>
In case of performing the date and time format check, it is recommend the use of @DateTimeFormat
annotation offered by Spring rather than the use of Bean Validation mechanism.
About how to use the @DateTimeFormat annotation, refer Date and time format conversion of fields.
It is also possible to check the date and time format using @Pattern annotation of the Bean Validation.
However, it is necessary to write the date and time format in regular expressions while using @Pattern
annotation. If you want to check the date and time that does not exist, the description is complicated.
Therefore @DateTimeFormat annotation is more simpler than the @Pattern annotation.
Since @DateTimeFormat annotation is one of the type conversion mechanism provided by Spring, instead of
the error messages of Bean Validation, the error message of the type mismatch exception
(TypeMismatchException) has been displayed on screen at the time of input error.
In order to avoid the actual exception message gets displayed on the screen, it is necessary to configure the error
message in the property file which will be displayed at the time of type mismatch is occurred.
For more detail, refer Type mismatch.
The method to validate nested Bean using Bean Validation is explained below.
“Ordering” process of an EC site is considered as an example. Rules for checking “Order” form are provided
below.
Use the same form class since receiverAddress and senderAddress are objects of the same class.
• Form class
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@Size(max = 5)
@Pattern(regexp = "[a-zA-Z0-9]*")
private String coupon;
@NotNull // (1)
@Valid // (2)
private AddressForm receiverAddress;
@NotNull
@Valid
private AddressForm senderAddress;
// omitted setter/getter
}
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull
@Size(min = 1, max = 50)
private String name;
@NotNull
@Size(min = 1, max = 10)
private String postcode;
@NotNull
@Size(min = 1, max = 100)
// omitted setter/getter
}
• Controller class
package com.example.sample.app.validation;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@RequestMapping("order")
@Controller
public class OrderController {
@ModelAttribute
public OrderForm setupForm() {
return new OrderForm();
}
}
}
• JSP
<!DOCTYPE html>
<html>
<%-- WEB-INF/views/order/orderForm.jsp --%>
<head>
<style type="text/css">
/* omitted (same as previous sample) */
</style>
</head>
<body>
<form:form modelAttribute="orderForm" method="post"
class="form-horizontal"
action="${pageContext.request.contextPath}/order/order">
<form:label path="coupon" cssErrorClass="error-label">Coupon Code:</form:label>
<form:input path="coupon" cssErrorClass="error-input" />
<form:errors path="coupon" cssClass="error-messages" />
<br>
<fieldset>
<legend>Receiver</legend>
<%-- (1) --%>
<form:errors path="receiverAddress"
cssClass="error-messages" />
<%-- (2) --%>
<form:label path="receiverAddress.name"
cssErrorClass="error-label">Name:</form:label>
<form:input path="receiverAddress.name"
cssErrorClass="error-input" />
<form:errors path="receiverAddress.name"
cssClass="error-messages" />
<br>
<form:label path="receiverAddress.postcode"
cssErrorClass="error-label">Postcode:</form:label>
<form:input path="receiverAddress.postcode"
cssErrorClass="error-input" />
<form:errors path="receiverAddress.postcode"
cssClass="error-messages" />
<br>
<form:label path="receiverAddress.address"
cssErrorClass="error-label">Address:</form:label>
<form:input path="receiverAddress.address"
cssErrorClass="error-input" />
<form:errors path="receiverAddress.address"
cssClass="error-messages" />
</fieldset>
<br>
<fieldset>
<legend>Sender</legend>
<form:errors path="senderAddress"
cssClass="error-messages" />
<form:label path="senderAddress.name"
cssErrorClass="error-label">Name:</form:label>
<form:input path="senderAddress.name"
cssErrorClass="error-input" />
<form:errors path="senderAddress.name"
cssClass="error-messages" />
<br>
<form:label path="senderAddress.postcode"
cssErrorClass="error-label">Postcode:</form:label>
<form:input path="senderAddress.postcode"
cssErrorClass="error-input" />
<form:errors path="senderAddress.postcode"
cssClass="error-messages" />
<br>
<form:label path="senderAddress.address"
cssErrorClass="error-label">Address:</form:label>
<form:input path="senderAddress.address"
cssErrorClass="error-input" />
<form:errors path="senderAddress.address"
cssClass="error-messages" />
</fieldset>
<form:button name="confirm">Confirm</form:button>
</form:form>
</body>
</html>
(2) Fields of nested bean are specified as [parent field name].[child field name].
Error message is displayed as follows if this form is sent with all the input fields left blank.
Add a field such that up to 3 addresses can be registered in “user registration” form explained at the beginning.
package com.example.sample.app.validation;
import java.io.Serializable;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
@NotNull
@Size(min = 1, max = 20)
private String name;
@NotNull
@Size(min = 1, max = 50)
@Email
private String email;
@NotNull
@Min(0)
@Max(200)
private Integer age;
@NotNull
@Size(min = 1, max = 3) // (1)
@Valid
private List<AddressForm> addresses;
// omitted setter/getter
}
(1) It is possible to use @Size annotation for checking size of collection as well.
• JSP
<!DOCTYPE html>
<html>
<%-- WEB-INF/views/user/createForm.jsp --%>
<head>
<style type="text/css">
/* omitted (same as previous sample) */
</style>
</head>
<body>
<form:input path="addresses[${status.index}].name"
cssErrorClass="error-input" />
<form:errors path="addresses[${status.index}].name"
cssClass="error-messages" />
<br>
<form:label path="addresses[${status.index}].postcode"
cssErrorClass="error-label">Postcode:</form:label>
<form:input path="addresses[${status.index}].postcode"
cssErrorClass="error-input" />
<form:errors path="addresses[${status.index}].postcode"
cssClass="error-messages" />
<br>
<form:label path="addresses[${status.index}].address"
cssErrorClass="error-label">Address:</form:label>
<form:input path="addresses[${status.index}].address"
cssErrorClass="error-input" />
<form:errors path="addresses[${status.index}].address"
cssClass="error-messages" />
<c:if test="${status.index > 0}">
<br>
<button class="remove-address-button">Remove</button>
</c:if>
</fieldset>
<br>
</c:forEach>
<button id="add-address-button">Add address</button>
<br>
<form:button name="confirm">Confirm</form:button>
</form:form>
<script type="text/javascript"
src="${pageContext.request.contextPath}/resources/vendor/js/jquery-1.10.2.min.js"></s
<script type="text/javascript"
src="${pageContext.request.contextPath}/resources/app/js/AddressesView.js"></script>
</body>
</html>
(2) Process the collection of child forms in a loop using <c:forEach> tag.
(3) Inside the loop, Specify the field of child form using [parent field
name][Index].[child field name].
• Controller class
package com.example.sample.app.validation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("user")
public class UserController {
@ModelAttribute
public UserForm setupForm() {
UserForm form = new UserForm();
List<AddressForm> addresses = new ArrayList<AddressForm>();
addresses.add(new AddressForm());
form.setAddresses(addresses); // (1)
return form;
}
(1) Edit the form object to display a single address form at the time of initial display of “user
registration” form.
• JavaScript
Below is the JavaScript to dynamically add address input field. However, the explanation of this code is
// webapp/resources/app/js/AddressesView.js
function AddressesView() {
this.addressSize = $('fieldset.address').size();
};
AddressesView.prototype.addAddress = function() {
var $address = $('fieldset.address');
var newHtml = addressTemplate(this.addressSize++);
$address.last().next().after($(newHtml));
};
AddressesView.prototype.removeAddress = function($fieldset) {
$fieldset.next().remove(); // remove <br>
$fieldset.remove(); // remove <fieldset>
};
function addressTemplate(number) {
return '\
<fieldset class="address">\
<legend>Address' + (number + 1) + '</legend>\
<label for="addresses' + number + '.name">Name:</label>\
<input id="addresses' + number + '.name" name="addresses[' + number + '].name" type="text
<label for="addresses' + number + '.postcode">Postcode:</label>\
<input id="addresses' + number + '.postcode" name="addresses[' + number + '].postcode" ty
<label for="addresses' + number + '.address">Address:</label>\
<input id="addresses' + number + '.address" name="addresses[' + number + '].address" type
<button class="remove-address-button">Remove</button>\
</fieldset>\
<br>\
';
}
$(function() {
var addressesView = new AddressesView();
$('#add-address-button').on('click', function(e) {
e.preventDefault();
addressesView.addAddress();
});
});
Error message is displayed as follows if this form is sent with all the input fields left blank.
Grouped validation
By creating validation group, input validation rules for a field can be specified for each group.
In the “new user registration” example, add “Must be an adult” rule for the age field. Add country field also
as “Adult” rules differ with country.
To specify group in Bean Validation, set any java.lang.Class object representing a group in group attribute
of the annotation.
• Form class
package com.example.sample.app.validation;
import java.io.Serializable;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
// (1)
public static interface Chinese {
};
@NotNull
@Size(min = 1, max = 20)
private String name;
@NotNull
@Size(min = 1, max = 50)
@Email
private String email;
@NotNull
@Min.List({ // (2)
@Min(value = 18, groups = Chinese.class), // (3)
@Min(value = 20, groups = Japanese.class),
@Min(value = 21, groups = Singaporean.class)
})
@Max(200)
private Integer age;
@NotNull
@Size(min = 2, max = 2)
private String country; // (4)
// omitted setter/getter
}
(2) @Min.List annotation is used to specify multiple @Min rules on a single field.
It is same even while using other annotations.
(3) Specify corresponding group class in the group attribute, in order to define rules for each
group.
When group attribute is not specified, javax.validation.groups.Default group is
used.
(4) Add a field which will be used to determine which group is to be applied.
• JSP
<form:button name="confirm">Confirm</form:button>
</form:form>
• Controller class
By giving a group name to @Validated annotation, the rules defined for that group will be applied.
package com.example.sample.app.validation;
import javax.validation.groups.Default;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.sample.app.validation.UserForm.Chinese;
import com.example.sample.app.validation.UserForm.Japanese;
import com.example.sample.app.validation.UserForm.Singaporean;
@Controller
@RequestMapping("user")
public class UserController {
@ModelAttribute
public UserForm setupForm() {
UserForm form = new UserForm();
return form;
}
(1) The parameter which is used as condition to dividing between the groups must be set to
param attribute.
(2) All the annotations, except @Min of age field, belongs to Default group; hence,
specifying Default is mandatory.
In this example, the check result of the combination of each input value is as follows.
18 cn OK
20 cn OK
jp OK
21 cn OK
jp OK
sg OK
Warning: Implementation of this Controller is inadequate; there is no handling when country value is
neither “cn”, “jp” nor “sg”. 400 error is returned when unexpected country value is encountered.
Next, we can think of a condition where the number of countries increase and adult condition of 18 years or more
is be set as a default rule.
• Form class
In order to specify a value to Default group (18 years or more), all groups should be specified explicitly
in other annotations as well.
package com.example.sample.app.validation;
import java.io.Serializable;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.validation.groups.Default;
import org.hibernate.validator.constraints.Email;
// omitted setter/getter
}
(1) Set all groups to annotations other than @Min of age field as well.
• JSP
No change in JSP
• Controller class
package com.example.sample.app.validation;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.sample.app.validation.UserForm.Japanese;
import com.example.sample.app.validation.UserForm.Singaporean;
@Controller
@RequestMapping("user")
public class UserController {
@ModelAttribute
public UserForm setupForm() {
UserForm form = new UserForm();
return form;
}
(1) When the field country does not have a value, the request is mapped to a method in which
Default group is specified in @Validated annotation.
(2) When the field country has a value, the request is mapped to a method in which Default
group is not included in @Validated annotation.
In the previous pattern, Default group is used in Controller class and in the later one, Default group is used
in form class.
If none of the above decision points are applicable, then using Bean Validation itself might not be a good
idea.After reviewing the design, usage of Spring Validator or implementation of validation in business logic should
be considered.
Note: In the examples explained so far, the switching of group validation is carried out using request parameter
and parameter that can be specified in @RequestMapping annotation. It is not possible to switch between
groups, if switching is to be performed based on permissions in authentication object or any information which
cannot be handled by @RequestMapping annotation.
@Controller
@RequestMapping("user")
public class UserController {
@Inject
SmartValidator smartValidator; // (1)
// omitted
if (result.hasErrors()) {
return "user/createForm";
}
return "user/createConfirm";
}
Since logic should not be written in Controller, if switching is possible using request parameters in
@RequestMappingannotation, SmartValidator must not be used.
Each one of above has been explained below. However, before that, their features and usage have been explained.
Spring Validator It is easy to create input validation for a Input validation implementation of unique
particular class. business requirements depending on specific
It is inconvenient to use in Controller. form
Bean Validation Creation of input validation is not as easy as Common input validation implementation of
Spring Validator. development project not depending on
It is easy to use in Controller. specific form
Implementation method is explained with the help of “reset password” process as an example.
Implement the following rules. Following rules are provided in the “reset password” form.
Check rule “Must be same as confirmPassword” is validation of correlated items as password field and
passwordConfirm field should have the same value.
• Form class
other than validation of correlated items, implement using Bean Validation annotation.
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull
@Size(min = 8)
private String password;
// omitted setter/getter
}
Note: Password is normally saved in database after hashing it, hence there is no need to check the maxi-
mum number of characters.
• Validator class
package com.example.sample.app.validation;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component // (1)
public class PasswordEqualsValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return PasswordResetForm.class.isAssignableFrom(clazz); // (2)
}
@Override
public void validate(Object target, Errors errors) {
if (errors.hasFieldErrors("password")) { // (3)
return;
}
if (!password.equals(confirmPassword)) { // (4)
errors.rejectValue(/* (5) */ "password",
/* (6) */ "PasswordEqualsValidator.passwordResetForm.password",
(2) Decide the argument is check target of this validator or not. Here PasswordResetForm
class is the target to be checked.
(3) If an error occurs at the target fields during a single item check, do not perform correlation
check in this Validator.
If it is necessary to perform the correlation check, this determination logic is not required.
(7) Set default message to be used when error message does not get resolved using code.
Note: Spring Validator implementation class should be placed in the same package as the Controller.
• Controller class
package com.example.sample.app.validation;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("password")
public class PasswordResetController {
@Inject
PasswordEqualsValidator passwordEqualsValidator; // (1)
@ModelAttribute
public PasswordResetForm setupForm() {
return new PasswordResetForm();
}
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.addValidators(passwordEqualsValidator); // (2)
}
• JSP
<!DOCTYPE html>
<html>
<%-- WEB-INF/views/password/resetForm.jsp --%>
<head>
<style type="text/css">
/* omitted */
</style>
</head>
<body>
<form:form modelAttribute="passwordResetForm" method="post"
class="form-horizontal"
action="${pageContext.request.contextPath}/password/reset">
<form:label path="password" cssErrorClass="error-label">Password:</form:label>
<form:password path="password" cssErrorClass="error-input" />
<form:errors path="password" cssClass="error-messages" />
<br>
<form:label path="confirmPassword" cssErrorClass="error-label">Password (Confirm):</f
<form:password path="confirmPassword"
cssErrorClass="error-input" />
<form:errors path="confirmPassword" cssClass="error-messages" />
<br>
<form:button>Reset</form:button>
</form:form>
</body>
</html>
Error message as shown below is displayed when form is sent by entering different values in password field and
confirmPassword fields.
Note: When <form:password> tag is used, data gets cleared at the time of redisplay.
Note: Error information can be set for multiple fields for correlation check. However, displaying error messages
and applying style must always be performed in a set and only one part of the tasks cannot not be performed.
When you want to apply style to both the fields wherein correlation check error has occurred however you want to
display only one error message, it can be done by setting a null string in the error message. An example is given
below wherein style is applied to password field and confirmPassword field and error message is displayed
only in password field.
package com.example.sample.app.validation;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
@Component
public class PasswordEqualsValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return PasswordResetForm.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
// omitted
if (!password.equals(confirmPassword)) {
// register a field error for password
errors.rejectValue("password",
"PasswordEqualsValidator.passwordResetForm.password",
"password and confirm password must be same.");
(2) Specify code name of error message. Specify a null string in the corresponding error message
at that time.
For message definition, refer Messages to be defined in application-messages.properties.
(3) Set a default message to be used when error message could not be resolved in the code.
A null string is set in the example above.
Note: When multiple forms are used in a single controller, model name should be specified in
@InitBinder("xxx") in order to limit the target of Validator.
@Controller
@RequestMapping("xxx")
public class XxxController {
// omitted
@ModelAttribute("aaa")
public AaaForm() {
return new AaaForm();
}
@ModelAttribute("bbb")
public BbbForm() {
return new BbbForm();
}
@InitBinder("aaa")
public void initBinderForAaa(WebDataBinder binder) {
// add validators for AaaForm
binder.addValidators(aaaValidator);
}
@InitBinder("bbb")
public void initBinderForBbb(WebDataBinder binder) {
// add validators for BbbForm
binder.addValidators(bbbValidator);
}
// omitted
}
Note: To change the check contents of correlated items check rules in accordance with a
validation group (for example: To implement correlated items check only when specific valida-
tion group is specified, etc.), it is better to switch the process within validate method by imple-
menting org.springframework.validation.SmartValidator interface instead of implementing
org.springframework.validation.Validator interface.
package com.example.sample.app.validation;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.SmartValidator;
@Component
public class PasswordEqualsValidator implements SmartValidator { // Implements SmartValidator
@Override
public boolean supports(Class<?> clazz) {
return PasswordResetForm.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
validate(target, errors, new Object[] {});
}
@Override
public void validate(Object target, Errors errors, Object... validationHints) {
// Check validationHints(groups) and apply validation logic only when 'Update.class'
if (ArrayUtils.contains(validationHints, Update.class)) {
PasswordResetForm form = (PasswordResetForm) target;
String password = form.getPassword();
String confirmPassword = form.getConfirmPassword();
// omitted...
}
}
}
Independent validation rules should be added to implement validation of correlated items using Bean Validation.
Error messages of Bean Validation in Spring MVC are resolved in the following order.
1. If there is any message which matches with the rule, among the messages defined in
org.springframework.context.MessageSource, then it is to be used as error message
(Spring rule).
For default rules of Spring, refer to “JavaDoc of DefaultMessageCodesResolver of
DefaultMessageCodesResolver”.
2. If message cannot be found as mentioned in step 1, then error message is acquired from the message
attribute of the annotation. (Bean Validation rule)
1. When the value of message attribute is not in “{message key}” format, use that text as error message.
2. When the value of message attribute is in “{message key}” format, search messages corresponding
to message key from ValidationMessages.properties under classpath.
2. When message corresponding to message key is not defined, use “{message key}” as error message
Considering that the following settings are done in applicationContext.xml, former is called as “application-
messages.properties” and latter is called “ValidationMessages.properties”.
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>i18n/application-messages</value>
</list>
</property>
</bean>
Warning: Multiple ValidationMessages.properties files should not exist directly under class
path.
If multiple ValidationMessages.properties files exist directly under class path, an appropriate mes-
sage may not be displayed, as either one file of them is read leaving rest of the files unread.
• When adopting multi project structure, please take care so that
ValidationMessages.properties file is not placed in multiple projects.
• When distributing common parts for Bean Validation as jar file, please take care so that
ValidationMessages.properties file is not included in jar file.
Further, when a project is created from Blank project of version 1.0.2.RELEASE or higher,
ValidationMessages.properties is stored directly under xxx-web/src/main/resources.
When ValidationMessages.properties is not provided, Default messages provided by Hibernate Validator is used.
Define messages for message key specified in message attribute of Bean Validation annotation of Validation-
Messages.properties under class path (normal src/main/resources).
It is explained below using the following form used at the beginning of Basic single item check.
@NotNull
@Size(min = 1, max = 20)
private String name;
@NotNull
@Size(min = 1, max = 50)
@Email
private String email;
@NotNull
@Min(0)
@Max(200)
private Integer age;
// omitted getter/setter
}
• ValidationMessages.properties
javax.validation.constraints.NotNull.message=is required.
# (1)
javax.validation.constraints.Size.message=size is not in the range {min} through {max}.
# (2)
javax.validation.constraints.Min.message=cannot be less than {value}.
javax.validation.constraints.Max.message=cannot be greater than {value}.
org.hibernate.validator.constraints.Email.message=is an invalid e-mail address.
(1) It is possible to embed the value of attributes specified in the annotation using {Attribute
name}.
When the form is sent with input fields left blank after adding the above settings, changed error messages are
displayed as shown below.
Add {0} to message as shown below when field name is to be included in error message.
• ValidationMessages.properties
javax.validation.constraints.NotNull.message="{0}" is required.
javax.validation.constraints.Size.message=The size of "{0}" is not in the range {min} thr
javax.validation.constraints.Min.message="{0}" cannot be less than {value}.
javax.validation.constraints.Max.message="{0}" cannot be greater than {value}.
org.hibernate.validator.constraints.Email.message="{0}" is an invalid e-mail address.
In this way, property name of form class gets displayed on the screen and so it is not user friendly. To display an
appropriate field name, it should be defined in application-messages.properties in the following format.
• application-messages.properties
name=Name
email=Email
age=Age
Note: Inserting field name in place of {0} is the functionality of Spring and not of Bean
Validation. Therefore, the settings for changing field name should be defined in application-
messages.properties(ResourceBundleMessageSource) which is directly under Spring management.
Tip: In Bean Validation 1.1, it is possible to use Expression Language (hereafter referred to as “EL expression”) in
a message specified in ValidationMessages.properties. Hibernate Validator 5.x supports Expression
Language 2.2 or higher version.
Executable EL expression version differs depending on the version of application server. Therefore when EL
expression is to be used, it should be used after confirming the version of EL expression supported by appli-
cation server.
# ...
# (1)
javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'o
# ...
Default messages to be used in system are defined in ValidationMessages.properties however, depending on the
screen, they may have to be changed from the default value.
Apply “Messages to be defined in ValidationMessages.properties” and override the message for email and age
field using the below settings.
• application-messages.properties
# override messages
# for email field
Size.userForm.email=The size of "{0}" must be between {2} and {1}.
# for age field
NotNull.userForm.age="{0}" is compulsory.
Min.userForm.age="{0}" must be greater than or equal to {1}.
Max.userForm.age="{0}" must be less than or equal to {1}.
# filed names
name=Name
email=Email
age=Age
Value of attributes of the annotation gets inserted after {1} onwards. Incidentally, index position of attribute
values are alphabetical ordering(ascending order) of attribute names.
Note: There are other formats as well for the message key format of application-messages.properties; however, if
it is used with the purpose of overwriting some default messages, it should be in [annotation name].[form
attribute name].[property name] format.
Other than standard check rules, bean validation has a mechanism to develop annotations for independent rules .
The method of creating independent rules can be widely classified into the following two broader criteria.
Basically, the below template can be used to create annotation for each rule.
package com.example.common.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface Xxx {
String message() default "{com.example.common.validation.Xxx.message}";
Xxx[] value();
}
}
Consider the following restrictions at the system level and domain level respectively.
These can be implemented by combining @Pattern, @Size, @Min, @Max of the existing rules.
However, if the same rules are to be used at multiple places, settings get distributed and maintainability worsens.
One rule can be created by combining multiple rules. There is an advantage to be able to have not only common
regular expression pattern and maximum/minimum values but also error message when an independent annotation
is created. By this, reusability and maintainability increases. Even if multiple rules are not combined, it also proves
beneficial if used only to give specific value to an attribute.
package com.example.common.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation // (1)
@Pattern(regexp = "[a-zA-Z0-9]*") // (2)
public @interface AlphaNumeric {
String message() default "{com.example.common.validation.AlphaNumeric.message}"; // (3)
(1) This will consolidate error messages and return only the message of this annotation at the time
of error.
package com.example.common.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Min;
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Min(value = 0)
public @interface NotNegative {
String message() default "{com.example.common.validation.NotNegative.message}";
• Implementation example of @UserId annotation which regulates the format of “User ID”.
package com.example.sample.domain.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Size(min = 4, max = 20)
@Pattern(regexp = "[a-z]*")
public @interface UserId {
String message() default "{com.example.sample.domain.validation.UserId.message}";
package com.example.sample.domain.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
@Documented
@Constraint(validatedBy = {})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Min(1)
@Max(150)
public @interface Age {
String message() default "{com.example.sample.domain.validation.Age.message}";
Note: If multiple rules are set in a single annotation, their AND condition forms the composite anno-
tation. In Hibernate Validator, @ConstraintComposition annotation is provided to implement OR
condition. Refer to Hibernate Validator document for details.
For the rules that cannot be implemented by combining @Pattern, @Size, @Min, @Max, implement
javax.validation.ConstraintValidator.
For example, rules that check ISBN (International Standard Book Number)-13 format are given.
• Annotation
package com.example.common.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { ISBN13Validator.class }) // (1)
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface ISBN13 {
String message() default "{com.example.common.validation.ISBN13.message}";
(1) Specify the ConstraintValidator implementation class which will get executed when
this annotation is used. Multiple constraints can also be specified.
• Validator
package com.example.common.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
@Override
public void initialize(ISBN13 constraintAnnotation) { // (2)
}
@Override
Tip: Example of Bean Validation of file upload is classified in this category. Further, in common library as well,
@ExistInCodeList is implemented in this way.
Check of correlated items, which span over multiple fields, can be done using Bean Validation as explained in
Correlation item check.
It is recommended to target generalized rules, in case of deciding to use Bean Validation for check of correlated
items.
An example of implementing the rule, “Contents of a field should match with its confirmation field” is given
below.
• Annotation
Define such that annotation for validation of correlated items can be used at class level as well.
package com.example.common.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { ConfirmValidator.class })
@Target({ TYPE, ANNOTATION_TYPE }) // (1)
@Retention(RUNTIME)
public @interface Confirm {
String message() default "{com.example.common.validation.Confirm.message}";
/**
* Field name
*/
String field(); // (2)
(1) Narrow down the target of using this annotation, such that it can be added only to class or
annotation.
• Validator
package com.example.common.validation;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
if (matched) {
return true;
} else {
context.disableDefaultConstraintViolation(); // (3)
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(field).addConstraintViolation(); // (4)
return false;
}
}
(2) Acquire property value from the form object through BeanWrapper.
For deprecated API of Bean Validation, refer to Bean Validation API Document (Deprecated API).
Note: As introduced in correlation item check using Spring Validator, even in Bean Validation, Set error infor-
An example is shown below wherein styles are applied to passwordfield and confirmPasswordfield in Bean
Validation and error message is displayed only for passwordfield.
// omitted
public class ConfirmValidator implements ConstraintValidator<Confirm, Object> {
private String field;
return false;
}
}
(1) Register error of confirmPasswordfield. A null string is set in error message at that time.
Check below for the changes, if the “Reset password” is re-implemented using @Confirm annotation.
• Form class
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.example.common.validation.Confirm;
@NotNull
@Size(min = 8)
private String password;
// omitted getter/setter
}
• Controller class
package com.example.sample.app.validation;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping("password")
public class PasswordResetController {
@ModelAttribute
public PasswordResetForm setupForm() {
return new PasswordResetForm();
}
Business logic check should implement Implementation in Service of domain layer and store result message in
ResultMessages object.
Accordingly, it is expected that, they will be normally displayed at the top of the screen.
However, there are cases, where business logic error message (such as, “whether the entered user name is already
registered”) of target input field is to be displayed next to the field. In such a case, service class is injected in
Validator class and business logic check is executed in ConstraintValidator.isValid.
An example of implementing, “whether the entered user name is already registered” in Bean Validation is shown
below.
• Service class
package com.example.sample.domain.service.user;
• Annotation
package com.example.sample.domain.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = { UnusedUserIdValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface UnusedUserId {
String message() default "{com.example.sample.domain.validation.UnusedUserId.message}";
• Validator class
package com.example.sample.domain.validation;
import javax.inject.Inject;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import org.springframework.stereotype.Component;
import com.example.sample.domain.service.user.UserService;
@Component // (1)
public class UnusedUserIdValidator implements
ConstraintValidator<UnusedUserId, String> {
@Inject // (2)
UserService userService;
@Override
public void initialize(UnusedUserId constraintAnnotation) {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
(3) Return the result of business logic error check. Process should be delegated to service class.
Logic must not be written directly in the isValid method.
Method Validation
A method to check validity of actual argument and return value of method using Bean Validation, is described. In
order to explain, the method is called Method Validation in this chapter. While performing defensive programming
etc, method I/O should be checked in the class other than Controller. If Bean Validation library is used at that time,
constraint annotation of Bean Validation used in Controller can be reused.
Application settings
When Method Validation offered by Spring Framework is used, a bean must be defined for
org.springframework.validation.beanvalidation.MethodValidationPostProcessor
class offered by Spring Framework.
Bean definition file which defines MethodValidationPostProcessor differs depending on where you use
the Method Validation.
Here, a setup example is given wherein Method Validation is used in the multi-project environment recommended
in this guideline.
• projectName-domain/src/main/resources/META-INF/spring/projectName-domain.xml
• projectName-web/src/main/resources/META-INF/spring/spring-mvc.xml
(1)
By using the wrapper Validator generated by this class, message management function (MessageSource)
offered by Spring Framework and DI container can be linked.
Tip: In Spring Framework, Method Validation for calling the method of Bean which is managed by DI container
is executed by using AOP system.
Note: In the example above, an identical Validator object (instance) is set for validator property of each
Bean, but this is not necessarily required. However, it is recommended to set an identical object (instance) unless
there is a reason to do otherwise.
When Method Validation is applied to the method, annotation which indicates inclusion of target method and the
constraint annotation of Bean Validation should be specified in class level and, method and dummy argument
respectively.
AOP which executes Method Validation is not applicable for “Application settings”. It is necessary to assign
@ org.springframework.validation.annotation.Validated annotation to interface or class in
order to apply AOP which executes Method Validation.
package com.example.domain.service;
import org.springframework.validation.annotation.Validated;
@Validated // (1)
public interface HelloService {
// ...
}
Tip: By specifying a group interface in value attribute of @Validated annotation, a validation belonging to
a specified group can alone be executed as well.
Further, the validation groups can be changed for each method by assigning Validated annotation in method
level.
Next, a method is described wherein constraint annotation of Bean Validation is specified in the method and
dummy argument. Basically,
• Method arguments
A basic specification method is described below. A method to specify annotation in the interface is introduced in
the description hereafter.
First, a method that specifies constraint annotation is described for the method using basic types (primitive type or
primitive wrapper type etc) as a signature of the method
package com.example.domain.service;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
@Validated
public interface HelloService {
// (2)
@NotNull
String hello(@NotNull /* (1) */ String message);
Next, a method that specifies constraint annotation of Bean Validation is described for the method using JavaBean
as a signature of method.
Note: The main point is to specify @javax.validation.Valid annotation. The specification method is
Service interface
package com.example.domain.service;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotNull;
@Validated
public interface HelloService {
@NotNull // (3)
@Valid // (4)
HelloOutput hello(@NotNull /* (1) */ @Valid /* (2) */ HelloInput input);
package com.example.domain.service;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import java.util.Date;
@NotNull
@Past
private Date visitDate;
@NotNull
private String visitMessage;
// ...
package com.example.domain.service;
import com.example.domain.model.User;
import java.util.Date;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
@NotNull
@Past
private Date acceptDate;
@NotNull
private String acceptMessage;
@Valid // (5)
private User user;
// ...
package com.example.domain.model;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import java.util.Date;
@NotNull
private String userId;
@NotNull
private String userName;
@Past
private Date dateOfBirth;
// ...
When ConstraintViolationException is thrown, the method generated from stack trace can be identi-
fied, however, basic violation details cannot be identified.
package com.example.app;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ConstraintViolationExceptionHandler {
// (1)
@ExceptionHandler
public String handleConstraintViolationException(ConstraintViolationException e){
// (2)
if (log.isErrorEnabled()) {
log.error("ConstraintViolations[\n{}\n]", e.getConstraintViolations());
}
return "common/error/systemError";
}
4.1.4 Appendix
Hibernate Validator provides additional validation annotations, in addition to the annotations defined in Bean
Validation.
Refer to Herefor the annotation list that can be used for validation.
@Null Arbitrary
@Null(groups={Update.cla
Validates that the target field is private String id;
null.
(Example: usage in group
validation)
@Pattern String
@Pattern(regexp = "[0-9]
Whether the target field matches private String tel;
with the regular expression
(In case of Hibernate Validator
implementation, it is also
possible to use it with arbitrary
implementation class of
CharSequence interface)
Tip: inclusive attribute of @DecimalMin and @DecimalMax annotation is, an attribute added from Bean
Validation 1.1.
By specifying true (to allow same value of specified threshold) to the default value of inclusive attribute,
compatibility with Bean Validation 1.0 is maintained.
Warning: In @Size annotation, characters represented by char type 2 (32 bits) called as surrogate pair are
not considered.
When a string consisiting of a surrogate pair is excluded from the check, adequate care must be taken since
number of characters that are counted are likely to be more than the actual number of characters.
For length of the string including surrogate pair, refer Fetching string length.
Warning: When following annotations provided by Hibernate Validator are used, if a default message is used,
a bug that the message is not generated correctly HV-881, HV-949) occurs.
• @CreditCardNumber(message is displayed, but WARN log is output)
• @LuhnCheck
• @Mod10Check
• @Mod11Check
• @ModCheck(deprecated API from 5.1.0.Final)
This bug occurs because of the flaws in message definitions provided by default, and it is possible to avoid
them by overwriting the default messages by an appropriate message.
In case of overwriting the default messages, it is advisable to define an appropriate message by creating
ValidationMessages.properties directly under the class path (normal src/main/resources).
For appropriate message definition, refer to: Modifications for Hibernate Validator 5.2 version (next minor
version upgrade).
A common library provides an independent annotation for verification. Here, how to specify input check rules
which use annotation provided by common library is explained.
@ByteMax
@ByteMin(1)
Implementation class of Verify whether byte length value @ByteMax(value = 100,
CharSequence is less than or equal to maximum charset = "Shift
(String, StringBuilder value. private String id;
etc)
[Annotation attribute]
long value - Specify
maximum value for byte length.
String charset - Specify
string character set used while
encoding the value in byte
sequence. Default value is
UTF-8.
For unit item check, whether a value is entered in the input field( should not be null) can be checked by
using @NotNull in combination. However, in correlated item check, the check like “if value is entered in one
field, value is entered forcefully in another field” cannot be implemented by using @NotNull alone. Hence,
@Compare provides requireBoth attribute which controls mandatory input for checking which can then be
used for implementing the check whenever required.
Also, when a value is not entered in the input field, requireBoth attribute can be used only when null is
bound. If a form is sent in Spring MVC when a value is not entered in input field of string, it must be noted that
empty string is bound in the form object by default instead of null. When a value is not entered in the string
field, refer Binding null to blank string field to bind null in form object instead of empty string.
Expected check requirements and configuration example are shown below using “checking whether period start
date is earlier than end date” as an example.
Only from is mandatory, however carry out compar- Assign @NotNullonly in from and use default
ison check when a value is entered in to as well. value ( false)in requireBoth attribute.
@Compare(left = "from", right = "to", operato
public class Period {
@NotNull
LocalDate from;
LocalDate to;
}
from and to are not both required, carry out com- Do not assign @NotNulland use default value
parison check only when values are entered in both ( false)in requireBoth attribute.
from and to. When the value is entered in only one @Compare(left = "from", right = "to", operato
of the fields, comparison check is not carried out. public class Period {
LocalDate from;
LocalDate to;
}
from and to are not both required, when values are Do not assign @NotNulland specify true in
entered in either of the fromor to, carry out compar- requireBoth attribute.
ison check by always entering values in both fields. @Compare(left = "from", right = "to", operato
public class Period {
LocalDate from;
LocalDate to;
}
Add a dependent library for the rules that are to be used. An example for how to add
terasoluna-gfw-validator is shown below.
<dependencies>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-validator</artifactId>
</dependency>
</dependencies>
# (1)
org.terasoluna.gfw.common.validator.constraints.ByteMin.message = must be greater than or equal to
org.terasoluna.gfw.common.validator.constraints.ByteMax.message = must be less than or equal to {v
org.terasoluna.gfw.common.validator.constraints.Compare.message = not match '{left}' and '{right}'
In the end, assign an annotation to JavaBean property as explained in Basic single item check.
Note: When validation cannot be implemented in Bean Validation due to invalid attribute value of annotation,
javax.validation.ValidationException is thrown. Modify the attribute value to a valid value by
referring the reason that is output in stack trace.
Any rule can be created by using check rules provided by common library.
An example is introduced below wherein @Confirm annotation independently implemented by Check rules for
correlated items is created by using check rules provided by common library.
Create @Confirm annotation by using @Compare as described in Creation of Bean Validation annotation by
combining existing rules.
package com.example.sample.domain.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.OverridesAttribute;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import org.terasoluna.gfw.common.validator.constraints.Compare;
@Documented
@Constraint(validatedBy = {})
@Target({ TYPE, ANNOTATION_TYPE }) // (1)
@Retention(RUNTIME)
@ReportAsSingleViolation // (2)
@Compare(left = "", right = "", operator = Compare.Operator.EQUAL, requireBoth = true) // (3)
public @interface Confirm {
@Documented
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface List {
Confirm[] value();
}
}
(1) Restrict the location where the annotation can be assigned to class or annotation.
(2) Message specified in message attribute of this annotation should be used at the time of error.
(5) Override left attribute of @Compare annotation and change attribute name to field.
(6) Similarly, override right attribute and change attribute name to confirmField.
Use annotation created above instead of annotation implemented in Check rules for correlated items.
package com.example.sample.app.validation;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import com.example.common.validation.Confirm;
@NotNull // (2)
@Size(min = 8)
private String password;
@NotNull // (3)
private String confirmPassword;
// omitted geter/setter
}
(2) Since @Confirm verification is passed when password field is null, perform null check by
assigning @NotNull annotation.
Type mismatch
Pertaining the non-String fields of the form object, if values which cannot be converted to the corresponding
data-type have been submitted, org.springframework.beans.TypeMismatchException is thrown.
In the “New user registration” example, The data-type of “Age” is Integer. However, if the value entered in
this field cannot be converted to an integer, error message is displayed as follows.
Cause of exception is displayed as it is and is not appropriate as an error message. It is possible to define
the error message for type mismatch in the properties file (application-messages.properties) which is read by
org.springframework.context.MessageSource.
# typemismatch
typeMismatch="{0}" is invalid.
typeMismatch.int="{0}" must be an integer.
typeMismatch.double="{0}" must be a double.
typeMismatch.float="{0}" must be a float.
typeMismatch.long="{0}" must be a long.
typeMismatch.short="{0}" must be a short.
typeMismatch.java.lang.Integer="{0}" must be an integer.
typeMismatch.java.lang.Double="{0}" must be a double.
typeMismatch.java.lang.Float="{0}" must be a float.
typeMismatch.java.lang.Long="{0}" must be a long.
typeMismatch.java.lang.Short="{0}" must be a short.
typeMismatch.java.util.Date="{0}" is not a date.
# field names
name=Name
email=Email
age=Age
Field name can be inserted in the message by specifying {0} in the message; This is as per the description in
Messages to be defined in application-messages.properties.
Default messages should be defined.
Tip: Refer to Javadoc of DefaultMessageCodesResolver for the details of message key rules.
When the form is sent with input fields left blank in Spring MVC, empty string instead of null binds to form
object by default.
In this case, the conditions like “blank is allowed but if specified, it must have at least 6 characters” is not satisfied
by the use of existing annotations.
To bind null instead of empty string to the properties without any input,
org.springframework.beans.propertyeditors.StringTrimmerEditor can be used as
follows.
@Controller
@RequestMapping("xxx")
public class XxxController {
@InitBinder
public void initBinder(WebDataBinder binder) {
// bind empty strings as null
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
// omitted ...
}
With the help of this configuration, it can be specified in the controller whether empty strings which are to be
considered as null or not.
@ControllerAdvice can be set as a configuration common to the entire project when you want to set reply empty
string to null in the entire project.
Tip: About attribute of @ControllerAdvice annotation added from Spring Framework 4.0
By specifying attribute of @ControllerAdvice annotation, it has been improved to allow flexibility in spec-
ifying Controller to apply a method implemented in a class wherein @ControllerAdvice is assigned. For
details about attribute refer to Attribute of @ControllerAdvice.
@ControllerAdvice
public class XxxControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
// bind empty strings as null
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
// omitted ...
}
When this setting is made, all empty String values set to String fields of form object become null.
Therefore it must be noted that, it becomes necessary to specify @NotNull in case of mandatory check.
When the Japanese message is to be handled directly without converting from Native to Ascii, it can be easily
implemented if it is linked with MessageSource of Spring.
If it is defined as below„ message read by MessageSource function can be used in Hibernate Validator.
• Bean definition
*-domain.xml
spring-mvc.xml
(1)
(2)
(3)
Same as (4).
(6)
Note: By using MessageSourcefunction, property file is not necessarily restricted to be placed just
under class path. Further, multiple property files can also be specified.
OS command injection, a type of security vulnerability and its countermeasures are explained here.
OS command injection is an issue which occurs when malicious commands are supplied to the user input string
resulting in illegal manipulation of the computer, when command execution strings are assembled from user input
strings in the application.
In Java, OS command injection can occur when following are used as the commands to be executed when com-
mands are executed using exec method of ProcessBuilderclass and Runtime class.
An example wherein OS command injection occurs when /bin/sh is used, is shown below.
(1) For example, when “exec.sh ; cat /etc/passwd” is input in the script, semicolon in the string is
interpreted as a delimiter by /bin/sh and “cat /etc/passwd” is executed.
Hence, /etc/passwd is likely to be output depending on how the standard output is handled.
Countermeasures
Running external processes should be avoided as much as possible in order to prevent occurrence of OS command
injection. However, if it is necessary to execute the external process due to certain circumstances, it must be
executed after implementing the countermeasures below.
• Commands which use /bin/sh (in case of Unix system) and cmd.exe (in case of Windows) should not
be executed as far as possible.
• Check whether the characters entered by user are allowed by the application using the whitelist system
The rules to check whether commands and arguments entered by the user are configured by the specified string
are shown below.
(1) Specify rule to allow only batch0X.sh(X is a single byte numeral from 0 to 9).
(2) Specify rule to allow only the strings configured from innocuous alphanumeric characters (\w)、=、
_) as arguments.
Note: In this example, directory traversal is prevented by establishing a rule wherein the path is not included in
the commands or arguments.
When @Pattern is used, regular expressions specified in @Pattern are output as error messages and are
considered invalid as messages due to following points.
A valid message is defined in application-messages.properties to conceal the logic by clarifying the significance
of error. For how to define a message, refer Messages to be defined in application-messages.properties.
This chapter describes exception handling mechanism for web applications created using this guideline.
4.2.1 Overview
This section illustrates handling of exceptions occurring within the boundary of Spring MVC. The scope of de-
scription is as follows:
1. Classification of exceptions
Classification of exceptions
The exceptions that occur when an application is running, can be broadly classified into following 3 categories.
(1) Exceptions wherein cause can be The exceptions wherein cause 1. Business exception
resolved if the user re-executes can be resolved if the user 2. Library exceptions that occurs
the operation (by changing input re-executes the operation, are during normal operation
values etc.) handled in application code.
(2) Exceptions wherein cause cannot The exceptions wherein cause 1. System Exception
be resolved even if the user cannot be resolved even if the 2. Unexpected System Exception
re-executes the operation user re-executes the operation, 3. Fatal Errors
are handled using the framework.
(3) Exceptions due to invalid The exceptions which occur due 1. Framework exception in case
requests from the client to invalid requests from the client, of invalid requests
are handled using the framework.
The exceptions which occur when the application is running, are handled using following 4 methods.
For details on flow of each handling method, refer to Basic Flow of Exception Handling.
(1) Use try-catch in the Use this in order to handle exceptions 1. When notifying partial
application code to carry out at request (Controller method) level. redo of a use case (from
exception handling. For details, refer to Basic flow when the middle)
Controller class handles the exception
at request level.
(2) Use @ExceptionHandler Use this when exceptions are to be 1. When notifying redo of a
annotation to carry out exception handled at use case (Controller) level. use case (from beginning)
handling in application code. For details, refer to Basic flow when the
Controller class handles the exception
at use case level.
(3) Use HandlerExceptionResolver Use this in order to handle exceptions 1. When notifying that the
mechanism provided by the at servlet level. system or application is not
framework to carry out exception When in a normal state
handling. <mvc:annotation-driven> is
specified, HandlerExceptionResolver 2. When notifying that the
uses automatically registered class, and request contents are invalid
SystemExceptionResolver
provided by common library.
For details, refer to Basic flow when the
framework handles the exception at
servlet level.
(4) Use the error-page function of the Use this in order to handle fatal errors 1. When a fatal error has
servlet container to carry out and exceptions which are out of the been detected
exception handling. boundary of Spring MVC.
For details, refer to Basic flow when the 2. When notifying that an
servlet container handles the exception exception has occurred in
at web application level. the presentation layer (JSP
etc.)
This is a class for handling exceptions which are not handled by HandlerExceptionResolver which is registered
automatically when <mvc:annotation-driven> is specified. Therefore, the order of priority of this class should be
set after DefaultHandlerExceptionResolver.
the above in earlier versions, it was necessary to define methods with @ExceptionHandler annotation
in Controller base class and inherit each Controller from the base class.
By specifying attribute of @ControllerAdvice annotation, it has been improved in such a way that
Controller that applies a method implemented in class with @ControllerAdvice, can be specified
flexibly. For details on attribute, refer to attribute of @ControllerAdvice.
1. When logic other than determining the View name and HTTP response code is necessary for exception
handling to be carried out at servlet level, (If only determining View name and HTTP response code is
sufficient, it can be implemented using SystemExceptionResolver)
2. In case of exception handling carried out at servlet level, when response data is to be created by serializing
the error model (JavaBeans) in JSON or XML formats without using template engines such as JSP. (Used
for error handling at the time of creating Controller for AJAX or REST).
4.2.2 Detail
1. Types of exceptions
Types of exceptions
Business exception
• If the reservation date is past the deadline when making travel reservations
• etc ...
• When handling is to be carried out at detailed level, exception class that inherits BusinessException should
be created.
• If the requirements cannot be met by the exception class provided by common library, a business exception
class should be created for each project.
Amongst the exceptions that occur in the framework and libraries, the exception which is likely to occur when
the system is operating normally.
The exceptions that occur in the framework and libraries cover the exception classes in Spring Framework or
other libraries.
Situations such are below are pre-defined and hence need not be dealt by the system administrator.
• Optimistic locking exception and pessimistic locking exception that occur if multiple users attempt to
update same data simultaneously.
• Unique constraint violation exception that occurs if multiple users attempt to register same data simultane-
ously.
• etc ...
• etc ...
Todo
Currently it has been found that unexpected errors occur if JPA(Hibernate) is used.
System Exception
Exception to notify that a state which should not occur when the system is operating normally has been
detected.
This exception should be generated in application layer and domain layer.
The exception needs to be dealt by the system administrator.
• If the master data, directories, files, etc. those should have pre-existed, do not exist
• Amongst the checked exceptions that occur in the framework, libraries, if exceptions classified as system
errors are caught (IOException during file operations etc.).
• etc ...
• When the error screen of View and HTTP response code need to be switched on the basis of the cause of
each error, SystemException should be inherited for each error cause and the mapping between the inherited
exception class and the error screen should be defined in the bean definition of SystemExceptionResolver.
• If the requirements are not met by the system exception class provided in the common library, a system
exception class should be created in each respective project.
Unchecked exception that does not occur when the system is operating normally.
Action by system administrator or analysis by system developer is necessary.
Unexpected system exceptions should not be handled (try-catch) in the application code.
• etc ...
• etc ...
Fatal Errors
Error to notify that a fatal problem impacting the entire system (application), has occurred.
Action/recovery by system administrator or system developer is necessary.
Fatal Errors (error that inherits java.lang.Error) must not be handled (try-catch) in the application code.
• etc ...
• etc ...
Exception to notify that the framework has detected invalid request contents.
This exception occurs in the framework (Spring MVC).
The cause lies at client side; hence it need not be dealt by the system administrator.
• Exception that occurs when a request path for which only POST method is permitted, is accessed using
GET method.
• Exception that occurs when type-conversion fails for the values extracted from URI using @PathVariable
annotation.
• etc ...
• org.springframework.web.HttpRequestMethodNotSupportedException (Exception
that occurs when the access is made through a HTTP method which is not supported).
• etc ...
Class for which HTTP status code is “4XX” in the list given at HTTP response code set by DefaultHandlerExcep-
tionResolver.
There are 6 types of exception handling patterns based on the purpose of handling.
(1)-(2) should be handled at use case level and (3)-(6) should be handled at the entire system (application) level.
(1) When notifying partial redo of a use case 1. Business exception Request
Application
(from middle)
code
(try-catch)
(2) When notifying redo of a use case (from 1. Business exception Use case
Application
beginning) 2. Library exceptions that
code
occurs during normal
operation
(@ExceptionHandler)
(4) When notifying that the request contents are 1. Framework exception in Servlet
Framework
invalid case of invalid requests
(5) When a fatal error has been detected 1. Fatal Errors Servlet Web application
container
(Handling
rules are
specified in
web.xml)
(6) When notifying that an exception has 1. All the exceptions and Servlet Web application
occurred in the presentation layer (JSP etc.) errors that occur in the container
presentation layer (Handling
4.2. Exception Handling rules are 411
specified in
web.xml)
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When partial redo (from middle) of a use case is to be notified, catch (try-catch) the exception in the application
code of Controller class and carry out exception handling at request level.
• When an order is placed through a shopping site, if business exception notifying stock shortage occurs.
In such a case, order can be placed if the no. of items to be ordered is reduced; hence display a screen on
which no. of items can be changed and prompt a message asking to change the same.
Figure.4.3 Figure - Handling method when notifying partial redo of a use case (from middle)
When redo of a use case (from beginning) is to be notified, catch the exception using @ExceptionHandler,
and carry out exception handling at use case level.
• At the time of changing the product master on shopping site (site for administrator), it has been changed
by other operator (in case of optimistic locking exception).
In such a case, the operation needs to be carried out after verifying the changes made by the other user;
hence display the front screen of the use case (for example: search screen of product master) and prompt a
message asking the user to perform the operation again.
Figure.4.4 Figure - Handling method when notifying redo of a use case (from beginning)
When an exception to notify that system or application is not in a normal state is detected, catch the exception
using SystemExceptionResolver and carry out exception handling at servlet level.
Note: Examples for notifying that the system or application is not in a normal state
• In case of a use case for connecting to an external system, if an exception occurs notifying that the
external system is blocked.
In such a case, since use case cannot be executed until external system resumes service, display the error
screen, and notify that the use case cannot be executed till the external system resumes service.
• When searching master information with the value specified in the application, if the corresponding
master information does not exist.
In such a case, there is a possibility of bug in master maintenance function or of error (release error) in
data input by the system administrator; hence display the system error screen and notify that a system
error has occurred.
• etc ...
Figure.4.5 Figure - Handling method when an exception to notify that system or application is not in a
normal state is detected
When notifying that an invalid request is detected by the framework, catch the exception using DefaultHandlerEx-
ceptionResolver, and carry out exception handling at servlet level.
Note: Example when notifying that the request contents are invalid
• When a URI is accessed using GET method, while only POST method is permitted.
In such a case, it is likely that the access has been made directly using the Favorites feature etc. of the
browser; hence display the error screen and notify that the request contents are invalid.
• etc ...
Figure.4.6 Figure - Handling method when notifying that the request contents are invalid
When a fatal error has been detected, catch the exception using servlet container and carry out exception handling
at web application level.
Figure.4.7 Figure - Handling method when a fatal error has been detected
When notifying that an exception has occurred in the presentation layer (JSP etc.)
When notifying that an exception has occurred in the presentation layer (JSP etc.), catch the exception using
servlet container and carry out exception handling at web application level.
Figure.4.8 Figure - Handling method when notifying an occurrence of exception in the presentation layer (JSP etc.)
1. Basic flow when the Controller class handles the exception at request level
2. Basic flow when the Controller class handles the exception at use case level
3. Basic flow when the framework handles the exception at servlet level
4. Basic flow when the servlet container handles the exception at web application level
Basic flow when the Controller class handles the exception at request level
In order to handle the exception at request level, catch (try-catch) the exception in the application code of the
Controller class.
Refer to the figure below:
It illustrates the basic flow at the time of handling a business exception
(org.terasoluna.gfw.common.exception.BusinessException) provided by common library.
Log is output using interceptor
(org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor) which
records that an exception holding the result message has occurred.
Figure.4.9 Figure - Basic flow when the Controller class handles the exception at request level
5. ResultMessagesLoggingInterceptor calls ExceptionLogger, and outputs log of warn level (monitoring log
and application log). ResultMessagesLoggingInterceptor class outputs logs only when sub exception (Busi-
nessException/ResourceNotFoundException) of ResultMessagesNotificationException occurs.
6. Controller class catches BusinessException, extracts the message information (ResultMessage) from
BusinessException and sets it to Model for screen display(6’).
9. JSP acquires message information (ResultMessage) using MessagesPanelTag and generates HTML
code for message display.
Basic flow when the Controller class handles the exception at use case level
In order to handle the exception at use case level, catch the exception using @ExceptionHandler of
Controller class.
Refer to the figure below.
It illustrates the basic flow at the time of handling an arbitrary exception (XxxException).
Log is output using interceptor
(org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor).
This interceptor records that the exception is handled using HandlerExceptionResolver.
Figure.4.10 Figure - Basic flow when the Controller class handles the exception at use case level
3. Exception (XxxException) is generated in the Service class called from Controller class.
6. Controller class generates message information (ResultMessage) and sets it to the Model for screen
display.
11. DispatcherServlet calls JSP that corresponds to the returned View name.
12. JSP acquires the message information (ResultMessage) using MessagesPanelTag and generates
HTML code for message display.
Basic flow when the framework handles the exception at servlet level
In order to handle the exception using the framework (at servlet level), catch the exception using
SystemExceptionResolver.
Refer to the figure below.
It illustrates the basic flow at the time of handling the system exception
(org.terasoluna.gfw.common.exception.SystemException) provided by common library
using org.terasoluna.gfw.web.exception.SystemExceptionResolver .
Log is output using interceptor
(org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor)
which records the exception specified in the argument of exception handling method.
Figure.4.11 Figure - Basic flow when the framework handles the exception at servlet level
4. A state equivalent to the system exception is detected in Service class; hence throw a SystemException.
10. DispatcherServlet calls the JSP corresponding to the returned View name.
11. JSP acquires the exception code from HttpServletRequest and inserts it in HTML code for message
display.
Basic flow when the servlet container handles the exception at web application level
In order to handle exceptions at web application level, catch the exception using servlet container.
Fatal errors, exceptions which are not handled using the framework (exceptions in JSP etc.) and exceptions
occurred in Filter are to be handled using this flow.
Refer to the figure below.
It illustrates the basic flow at the time of handling java.lang.Exception by “error page”.
Log is output using servlet filter
(org.terasoluna.gfw.web.exception.ExceptionLoggingFilter) which records that an
unhandled exception has occurred.
6. ServletContainer catches ServletException and outputs logs to server log. Log level varies depending on
the application server.
8. The response generated by the View which is called by the ServletContainer, is displayed.
Figure.4.12 Figure - Basic flow when servlet container handles the exception at web application level
For exception handling classes provided by common library, refer to Exception handling classes provided by the
common library.
1. Application Settings
Application Settings
The application settings required for using exception handling are shown below.
Further, these settings are already done in blank project. Hence, it will work only by doing changes given in
[Location to be customized for each project]section.
1. Common Settings
Common Settings
1. Add bean definition of logger class (ExceptionLogger) which will output exception log.
• applicationContext.xml
(2) Specify mapping between name of the exception to be handled and applicable “Exception Code
(Message ID)”.
In the above example, if the “BusinessException” is included in the class name of exception
class (or parent class), “w.xx.fw.8001” will be the “Exception code (Message ID)” and if
“ResourceNotFoundException” is included in the class name of exception class (or parent
class), “w.xx.fw.5001” will be the “Exception code (Message ID)”.
• logback.xml
(1) Specify appender definition used for outputting monitoring log. In the above example, an
appender to be output to a file has been specified, however the appender used should be
consider as per the system requirements.
[Location to be customized for each project]
(2) Specify logger definition for monitoring log. When creating ExceptionLogger, if any logger
name is not specified, the above settings can be used as is.
(3) Specify output level. In ExceptionLogger, 3 types of logs of info, warn, error are output;
however, the level specified should be as per the system requirements. The guideline
recommends Error level.
[Location to be customized for each project]
<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="APPLICATION_LOG_FILE" /> <!-- (4) -->
</root>
(1) Specify appender definition used for outputting application log. In the above example, an
appender to be output to a file has been specified, however the appender used should be
consider as per the system requirements.
[Location to be customized for each project]
(2) Specify logger definition for application log. When creating ExceptionLogger, if any logger
name is not specified, the above settings can be used as is.
(3) Specify the output level. In ExceptionLogger, 3 types of logs of info, warn, error are output;
however, the level specified should be as per the system requirements. This guideline
recommends info level.
[Location to be customized for each project]
(4) Log is transmitted to root since appender is not specified for the logger set in (2). Therefore,
specify an appender which will act as output destination. Here, it will be output to “STDOUT”
and “APPLICATION_LOG_FILE”.
[Location to be customized for each project]
• xxx-domain.xml
(2) Inject the logger which outputs exception log. Specify “exceptionLogger” defined in
applicationContext.xml.
(3) Apply ResultMessagesLoggingInterceptor for the method of the Service class (with
@Service annotation).
Add to bean definition, the class (SystemExceptionResolver) used for handling the exceptions which are
not handled by HandlerExceptionResolver registered automatically when <mvc:annotation-driven> is specified. .
• spring-mvc.xml
</map>
</property>
<property name="statusCodes"> <!-- (5) -->
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/businessError" value="409" />
<entry key="common/error/transactionTokenError" value="409" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
<property name="defaultErrorView" value="common/error/systemError" /> <!-- (6) -->
<property name="defaultStatusCode" value="500" /> <!-- (7) -->
</bean>
(2) Inject the object that resolves exception code (Message ID). Specify “exceptionCodeResolver”
defined in applicationContext.xml.
(3) Specify the order of priority for handling. The value can be “3”. When
<mvc:annotation-driven> is specified, automatically registered class is given higher
priority.
(4) Specify the mapping between name of the exception to be handled and View name.
In the above settings, if class name of the exception class (or parent class) includes
”.DataAccessException”, “common/error/dataAccessError” will be treated as View name.
If the exception class is “ResourceNotFoundException”,
“common/error/resourceNotFoundError” will be treated as View name.
[Location to be customized for each project]
(5) Specify the mapping between View name and HTTP status code.
In the above settings, when View name is “common/error/resourceNotFoundError”, “404(Not
Found)” becomes HTTP status code.
[Location to be customized for each project]
• spring-mvc.xml
(2) Inject the logger object which outputs exception log. Specify the “exceptionLogger” defined in
applicationContext.xml.
As per default settings, this class will not output the log for common library provided
org.terasoluna.gfw.common.exception.ResultMessagesNotificationException
class and its subclasses.
The reason the exceptions of the sub class of
ResultMessagesNotificationException are excluded from the log output is
because their log output is carried out by
org.terasoluna.gfw.common.exception.ResultMessagesLoggingInterceptor.
If default settings need to be changed, refer to About
HandlerExceptionResolverLoggingInterceptor settings.
Filter class (ExceptionLoggingFilter) used to output log of fatal errors and exceptions that are out
of the boundary of Spring MVC should be added to bean definition and web.xml.
• applicationContext.xml
(2) Inject the logger object which outputs exception log. Specify the “exceptionLogger” defined in
applicationContext.xml.
• web.xml
<filter>
<filter-name>exceptionLoggingFilter</filter-name> <!-- (1) -->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- (2
</filter>
<filter-mapping>
<filter-name>exceptionLoggingFilter</filter-name> <!-- (3) -->
<url-pattern>/*</url-pattern> <!-- (4) -->
</filter-mapping>
(1) Specify the filter name. Match it with the bean name of ExceptionLoggingFilter
defined in applicationContext.xml.
(3) Specify the name of the filter to be mapped. The value specified in (1).
(4) Specify the URL pattern to which the filter must be applied. It is recommended that you use /*
for outputting log of fatal errors and exceptions that are out of the boundary of Spring MVC.
• Output Log
Add error-page definition for Servlet Container in order to handle error response (HttpServletResponse#sendError)
received through default exception handling functionality of Spring MVC, fatal errors and the exceptions that are
out of the boundary of Spring MVC.
• web.xml
Add definitions in order to handle error response (HttpServletResponse#sendError) received through de-
fault exception handling functionality of Spring MVC.
<error-page>
<!-- (1) -->
<error-code>404</error-code>
<!-- (2) -->
<location>/WEB-INF/views/common/error/resourceNotFoundError.jsp</location>
</error-page>
(2) Specify the file name. It should be specified with the path from Web application root. In the
above settings, “${WebAppRoot}/WEB-INF/views/common/error/resourceNotFoundError.jsp”
will be the View file.
[Location to be customized for each project]
Add definitions in order to handle fatal errors and exceptions that are out of the boundary of Spring MVC.
<error-page>
<!-- (3) -->
<location>/WEB-INF/views/common/error/unhandledSystemError.html</location>
</error-page>
(3) Specify the file name. Specify with a path from web application root. In the above settings,
“${WebAppRoot}/WEB-INF/views/common/error/unhandledSystemError.html” will be the
View file.
[Location to be customized for each project]
If a fatal error occurs, there is high possibility of getting another error if the path of dynamic contents is specified.;
hence in location tag, it is recommended that you specify a path of static contents such as HTML and not
dynamic contents such as JSP.
If an unexpected error response (HttpServletResponse#sendError) occurs after carrying out the above settings,
there may be cases wherein it cannot be determined what kind of error response occurred.
Error screen specified in location tag is displayed; however if the cause of error cannot be identified from logs, it
is possible to verify the error response (HTTP response code) on screen by commenting out the above settings.
When it is necessary to individually handle the exceptions that are out of the boundary of Spring MVC, definition
of each exception should be added.
<error-page>
<!-- (4) -->
<exception-type>java.io.IOException</exception-type>
<!-- (5) -->
<location>/WEB-INF/views/common/error/systemError.jsp</location>
</error-page>
(5) Specify the file name. Specify it using a path from web application root. In above case,
“${WebAppRoot}/WEB-INF/views/common/error/systemError.jsp” will be the View file.
[Location to be customized for each project]
The coding points in Service when handling the exceptions are given below.
• It is recommended that you generate the business exception by detecting violation of business rules in
logic.
• When it is required by the API specification of underlying base framework or existing layer of application,
that violation of business rule be notified through an exception, then it is OK to catch the exception and
throw it as business exception.
Use of an exception to control the processing flow lowers the readability of overall logic and thus may
reduce maintainability.
Warning:
• It is assumed that business exception is generated in Service by default. In AOP settings, log of business
exception occurred in class with @Service annotation, is being output. Business exceptions should
not be logged in Controller etc. This rule can be changed if needed in the project.
• xxxService.java
...
@Service
public class ExampleExceptionServiceImpl implements ExampleExceptionService {
@Override
public String throwBusinessException(String test) {
...
// int stockQuantity = 5;
// int orderQuantity = 6;
(2) If there is a violation, generate ResultMessages. In the above example, ResultMessages of error
level are being generated.
For the details on method of generating ResultMessages, refer to Message Management.
(3) Add ResultMessage to ResultMessages. Specify message ID as 1st argument (mandatory) and
value to be inserted in message as 2nd argument (optional).
The value to be inserted in message is a variable-length parameter; hence multiple values can
be specified.
Tip: For the purpose of explanation, xxxService.java logic is written in steps (2)-(4) as shown
above, however it can also be written in a single step.
• xxx.properties
e.ad.od.5001 = Order number is higher than the stock quantity={0}. Change the order number.
// stackTrace omitted
...
Displayed screen
Warning: It is recommended that you handle business exception in Controller and display a message
on each business screen. The above example illustrates a screen which is displayed when the exception
is not handled in Controller.
try {
order(orderQuantity, itemId );
} catch (StockNotEnoughException e) { // (1)
throw new BusinessException(ResultMessages.error().add(
"e.ad.od.5001", e.getStockQuantity()), e); // (2)
}
(1) Catch the exception that occurs when a business rule is violated.
(2) If the system is in an abnormal state, specify the exception code (message ID) as 1st argument.
Specify exception message as 2nd argument to generate SystemException.
In the above example, the value of variable “itemId” is inserted in message text.
Displayed screen
Note: It is recommended to have a common system error screen rather than creating multiple screens for
system errors.
The screen mentioned in this guideline displays a (business-wise) message ID for system errors and has
a fixed message. This is because there is no need to inform the details of error to the operator and it is
sufficient to only convey that the system error has occurred. Therefore, in order to enhance the response
for inquiry against system errors, the Message ID which acts as a key for the message text is displayed on
the screen, in order to make the analysis easier for the development side. Displayed error screens should
be designed in accordance with the UI standards of each project.
try {
return new File(preUploadDir.getFile(), key);
} catch (FileNotFoundException e) { // (1)
throw new SystemException("e.ad.od.9007",
"not found upload file. file is [" + preUploadDir.getDescription() + "]."
e); // (2)
}
(2) Specify the exception code (message ID), message, Cause Exception (e) to generate
SystemException.
When it is necessary to catch the exception to continue the execution, the exception should be logged before
continuing the execution.
When fetching customer interaction history from external system fails, the process of fetching information other
than customer history can still be continued. This is illustrated in the following example.
In this example, although fetching of customer history fails, business process does not have to be stopped and
hence the execution continues.
@Inject
ExceptionLogger exceptionLogger; // (1)
// ...
// (4)
Customer customer = customerRepository.findOne(customerId);
// ...
(3) Output the handled exception to log. In the example, log method is being called; however if the
output level is known in advance
and if there is no possibility of any change in output level, it is ok to call info, warn, error
methods directly.
Warning: When log() is used in exceptionLogger, since it will be output at error level; by default,
it will be output in monitoring log also.
As shown in following example, if there is no problem in continuing the execution, and if monitoring log is being
monitored through application monitoring, it should be set to a level such that it will not get monitored at log
output level or defined such that it does not get monitored from the log content (log message).
} catch (RestClientException e) {
exceptionLogger.info(e);
}
As per default settings, monitoring log other than error level will not be output. It is output in application
log as follows:
The coding points in Controller while handling the exceptions are given below.
Handle the exception at request level and set the message related information to Model.
Then, by calling the method for displaying the View, generate the model required by the View and determine the
View name.
// omitted
// omitted
(1) As of the parameters of the method, define Model in argument as an object which will be used
to link the error information with View.
(4) Call the method for displaying the View at the time of error. Fetch the model and View name
required for View display and then return the View name to be displayed.
Handle the exception at use case level and generate ModelMap (ExtendedModelMap) wherein message related
information etc. is stored.
Then, by calling the method for displaying the View, generate the model required by the View and determine the
View name.
@ExceptionHandler(BusinessException.class) // (1)
@ResponseStatus(HttpStatus.CONFLICT) // (2)
public ModelAndView handleBusinessException(BusinessException e) {
ExtendedModelMap modelMap = new ExtendedModelMap(); // (3)
modelMap.addAttribute(e.getResultMessages()); // (4)
String viewName = top(modelMap); // (5)
return new ModelAndView(viewName, modelMap); // (6)
}
(1) The exception class which need to be handled should be specified in the value attribute of
@ExceptionHandler annotation. You can also specify multiple exceptions which are in the
scope of handling.
(2) Specify the HTTP status code to be returned to value attribute of @ResponseStatus
annotation. In the example, “409: Conflict” is specified.
(3) Generate ExtendedModelMap as an object to link the error information and model information
with View.
(5) Call the method to display the View at the time of error and fetch model and View name
necessary for View display.
(6) Generate ModelAndView wherein View name and Model acquired in steps (3)-(5) are stored
and then return the same.
The coding points in JSP while handling the exceptions are given below.
Tip: When Internet Explorer is a support browser, implementation should be such that size of the HTML re-
sponded as error screen should be 513 bytes or more.
This is because in case of Internet Explorer, if the following three conditions such as
are satisfied, the mechanism is such that friendly messages created by Internet Explorer will be displayed.
The example below illustrates implementation at the time of outputting ResultMessages to an arbitrary location.
The example below illustrates implementation at the time of displaying exception code (message ID) and fixed
message at an arbitrary location.
<p>
<c:if test="${!empty exceptionCode}"> <!-- (1) -->
[${f:h(exceptionCode)}] <!-- (2) -->
</c:if>
<spring:message code="e.cm.fw.9999" /> <!-- (3) -->
</p>
(1) Check whether exception code (message ID) exists. Perform existence check when the
exception code (message ID) is enclosed with symbols as shown in the above example.
• When a system exception occurs, it is recommended that you display a message which would only
convey that a system exception has occurred, without outputting a detailed message from which cause
of error can be identified or guessed.
• On displaying a detailed message from which cause of error can be identified or guessed, the vulnera-
bilities of system are likely to get exposed.
• When a system exception occurs, it is desirable to output an exception code (message ID) instead of a
detailed message.
• By outputting the exception code (message ID), it is possible for development team to respond quickly
to the inquiries from system user.
• Only a system administrator can identify the cause of error from exception code (message ID); hence
the risk of exposing the vulnerabilities of system is lowered.
4.2.5 Appendix
In addition to the classes provided by Spring MVC, the classes for carrying out exception handling are being
provided by the common library.
The roles of classes are as follows:
(1) ExceptionCode An interface for resolving exception code (message ID) of exception
Resolver class.
An exception code is used for identifying the exception type and is
expected to be output to system error screen and log.
It is referenced from ExceptionLogger,
SystemExceptionResolver.
Warning:
• Note that when a part of FQCN is specified, it may be
matched with classes which were not assumed.
• Note that when the name of parent class is specified, all
the child classes will also be matched.
(4) ExceptionLevel Interface for resolving exception level (log level) of exception class.
Resolver Exception level is a code for identifying the level of exception and is used
to switch the output level of log.
It is referenced from ExceptionLogger.
(13) SystemException This is a class for handling the exceptions that will not be handled by
Resolver HandlerExceptionResolver, which is registered automatically
when <mvc:annotation-driven> is specified.
It inherits SimpleMappingExceptionResolver provided by
Spring MVC and adds the functionality of referencing ResultMessages of
exception code from View.
(14) HandlerException This is an Interceptor class to output the log of exceptions handled by
ResolverLogging HandlerExceptionResolver.
Interceptor In this Interceptor class, the output level of log is switched based on the
classification of HTTP response code resolved by
HandlerExceptionResolver.
1. When it is “100-399”, log is output at INFO level.
2. When it is “400-499”, log is output at WARN level.
3. When it is “500-”, log is output at ERROR level.
4. When it is “-99”, log is not output.
By using this Interceptor, it is possible to output the log of all exceptions
which are within the boundary of Spring MVC.
Log is output using ExceptionLogger.
(15) ExceptionLogging This is a Filter class to output the log of fatal errors and exceptions that
Filter are out of the boundary of Spring MVC.
All logs are output at ERROR level.
When this Filter is used, fatal errors and all exceptions that are out of the
boundary of Spring MVC can be output to log.
Log is output using ExceptionLogger.
This section describes the settings which are not explained above. The settings should be performed depending
on the requirements.
(1) Attribute name Specify the attribute name (String) used for setting
resultMessagesAttribute
of result message of businessException to Model.
message This attribute name is used for accessing result
message in View(JSP).
exceptionCode
(2) Attribute name exceptionCode Specify the attribute name (String) used for setting
of exception Attribute the exception code (message ID) to
code (message HttpServletRequest.
ID) This attribute name is used for accessing the
exception code (message ID) in View(JSP).
X-Exception-Code
(3) Header name of exceptionCode Specify the header name (String) used for setting
exception code Header the exception code (message ID) to response
(message ID) header of HttpServletResponse.
exception
(4) Attribute name Specify the attribute name (String) used for setting
exceptionAttribute
of exception the handled exception object to Model.
object This attribute name is used for accessing the
exception object in View(JSP).
(5) List of handler mappedHandlers Specify the object list (Set) of handlers that use Not specified
(Controller) this ExceptionResolver.
objects to be Only the exceptions in the specified handler When specified, the
used as this Ex- objects will be handled. operation is not
ceptionResolver This setting should not be specified. guaranteed.
(6) List of handler Specify the class list (Class[]) of handlers that use Not specified
mappedHandlerClasses
(Controller) this ExceptionResolver.
classes that use Only the exceptions that occur in the specified
When specified, the
this Exception- handler classes are handled. operation is not
448 4 Web Application Development Features
Resolver This setting should not be specified guaranteed.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
If the message set by handling the exception using SystemExceptionResolver and the message set by handling
the exception in application code, both are to be output in separate messagesPanel tags in View(JSP), then
specify an attribute name exclusive to SystemExceptionResolver.
The example below illustrates settings and implementation when changing the default value to
“resultMessagesForExceptionResolver”.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
• jsp
(2) Specify attribute name that was set in SystemExceptionResolver, in message attribute name
(messagesAttributeName).
When default attribute name is being used in the application code, a different value should be set in order to
avoid duplication. When there is no duplication, there is no need to change the default value.
The example below illustrates settings and implementation when changing the default value to
“exceptionCodeForExceptionResolver”.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
• jsp
<p>
<c:if test="${!empty exceptionCodeForExceptionResolver}"> <!-- (2) -->
[${f:h(exceptionCodeForExceptionResolver)}] <!-- (3) -->
</c:if>
<spring:message code="e.cm.fw.9999" />
</p>
When default header name is being used, set a different value in order to avoid duplication. When there is no
duplication, there is no need to change the default value.
The example below illustrates settings and implementation when changing the default value to
“X-Exception-Code-ForExceptionResolver”.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
When default attribute name is being used in the application code, set a different value in order to avoid
duplication. When there is no duplication, there is no need to change the default value.
The example below illustrates settings and implementation when changing the default value to
“exceptionForExceptionResolver”.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
• jsp
<p>[Exception Message]</p>
<p>${f:h(exceptionForExceptionResolver.message)}</p> <!-- (2) -->
When header for cache control is to be added to HTTP response, specify true: Yes.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
(1) Set the cache control flag (preventResponseCaching) of HTTP response to true: Yes.
If cache control flag of HTTP response is set to Yes, the following HTTP response header is output.
Cache-Control:no-store
This section describes the settings which are not explained above. The settings should be performed depending
on the requirements.
and sub class occurs, the same is not logged in this and its sub classes are
class. logged by
ResultMessagesLogg
Only the exceptions which are logged at a different
hence, as default
location (using different mechanism) should be
settings, they are
specified here.
being excluded.
The settings when exception classes provided in the project are to be excluded from the scope of logging, are as
follows:
• spring-mvc.xml
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
<property name="ignoreExceptions">
<set>
<!-- (1) -->
<value>org.terasoluna.gfw.common.exception.ResultMessagesNotificationException</v
<!-- (2) -->
<value>com.example.common.XxxException</value>
</set>
</property>
</bean>
The settings when all the exception classes are to be logged are as follows:
• spring-mvc.xml
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
<!-- (3) -->
<property name="ignoreExceptions"><null /></property>
</bean>
The mapping between framework exceptions handled using DefaultHandlerExceptionResolver and HTTP status
code is shown below.
(1) 404
org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException
(2) 405
org.springframework.web.HttpRequestMethodNotSupportedException
(5) 400
org.springframework.web.bind.MissingServletRequestParameterException
(9) 400
org.springframework.http.converter.HttpMessageNotReadableException
(10) 500
org.springframework.http.converter.HttpMessageNotWritableException
(11). 400
org.springframework.web.bind.MethodArgumentNotValidException
4.3.1 Overview
In Web applications, data is exchanged between the client and the server, using HTTP.
HTTP itself does not have a feature to physically maintain a session. Instead, it provides a mechanism wherein, a
logical session is maintained by linking a session identifier value (session ID), between the client and the server.
Cookie or request parameter is used to link the session ID between client and server.
The figure below illustrates establishment of logical session.
(1) Web browser (Client) accesses the Web application (Server) when session is not established.
(2) Web application creates HttpSession object for storing the session with Web browser.
Session ID is issued at the time of HttpSession object creation.
(3) Web application stores the data sent by the Web browser in HttpSession object.
(4) Web application sends a response to the Web browser. By setting “JSESSIONID = Issued
4.3. Session Management 457
session ID” in the “Set-Cookie” header of response, session ID is linked to the Web Browser.
Linked session ID is stored in Cookie.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
In JavaEE Serlvet specifications, the default parameter name to link a session ID is JSESSIONID.
Session lifecycle
Instead of implementing as Controller process, session lifecycle control (create, discard, timeout detection)
is implemented by using the processes provided by framework or common library.
Generating a session
When Web application is created by the method recommended in this guideline, session can be generated by any
one of the following processes.
1.
Authentication/authorization process provided by Spring Security.
Timing and status of session generation can be specified by Spring Security settings.
For details on session management in Spring Security, refer to How to use.
2.
CSRF token check process provided by Spring Security
When a session is already established, new session is not generated.
For details on CSRF token check, refer to CSRF Countermeasures.
3.
Transaction token check process provided by common library.
When a session is already established, new session is not generated.
For details on Transaction token check, refer to Double Submit Protection.
4.
Process for passing a model (form object, domain object etc.) to redirect destination request, by
using addFlashAttribute method of RedirectAttributes interface.
When a session is already established, new session is not generated.
For details on RedirectAttributes and Flash scope, refer to Passing data while
redirecting request.
5.
Process for storing a model (form object, domain object etc.) in a session, using
@SessionAttributes annotation.
The specified model (form object, domain object etc.) is stored in session. When a session is
already established, new session is not generated.
For details on how to use @SessionAttributes annotation, refer to Using
@SessionAttributes annotation.
6.
Process that uses session-scoped bean in Spring Framework.
When a session is already established, new session is not generated.
For details on how to use session-scoped bean, refer to Using session-scoped bean of Spring
Framework.
Note: In Sr. Nos. 4, 5, 6 mentioned above, whether the session is used or not is specified by Con-
troller implementation whereas, session generation timing is controlled by the framework. In other words,
HttpSession API need not be used directly as Controller process.
When Web application is created by the methods recommended in this guideline, attributes (objects) are stored in
session by any one of the following processes.
1.
Authentication process provided by Spring Security.
Authenticated user information is stored in the session.
For details on Spring Security authentication process, refer to Authentication.
2.
CSRF token check process provided by Spring Security.
Issued token value is stored in session.
For details on CSRF token check, refer to CSRF Countermeasures.
3.
Transaction token check process provided by common library.
Issued token value is stored in session.
For details on Transaction token check, refer to Double Submit Protection.
4.
Process for passing a model (form object, domain object etc.) to redirect destination request, by
using addFlashAttribute method of RedirectAttributes interface.
Object specified in the addFlashAttribute method of RedirectAttributes interface, is
stored in an area called Flash scope in the session.
For details on RedirectAttributes and Flash scope, refer to Passing data while
redirecting request.
5.
Process for storing a model (form object, domain object etc.) in session, using
@SessionAttributes annotation.
The specified model (form object, domain object etc.) is stored in session.
For the details on how to use @SessionAttributes annotation, refer to, Using
@SessionAttributes annotation.
6.
Process that uses session-scoped bean in Spring Framework.
Session-scoped bean is stored in session.
For details on how to use session-scoped bean, refer to Using session-scoped bean of Spring
Framework.
Note: Timing to store the object in session is controlled by the framework. Hence, setAttribute method of
HttpSession object is not called as Controller process.
When Web application is created by the methods recommended in this guideline, attributes (objects) are deleted
from a session by any one of the following processes.
1.
Logout process provided by Spring Security.
Authenticated user information is deleted from the session.
For details on Spring Security logout process, refer to Authentication.
2.
Transaction token check process provided by common library.
When the value of issued token exceeds the upper limit allocated to NameSpace, token value
that is not in use, is deleted from the session.
For details on Transaction token check, refer to Double Submit Protection.
3.
Redirect process after the object is stored in Flash scope.
Object specified in the addFlashAttribute method of RedirectAttributes interface, is
deleted from the Flash scope area of session.
4.
Framework process, after the setComplete method of SessionStatus object is called as
Controller process.
Object specified by @SessionAttributes annotation is deleted from the session.
Note: Timing to delete the object is controlled by the framework. Hence, removeAttribute method of
HttpSession object is not called as Controller process.
Discarding a session
When Web application is created by the methods recommended in this guideline, session is discarded using any
one of the following processes.
1.
Logout process provided by Spring Security.
For details on Spring Security logout process, refer to Authentication.
2.
Process for detecting session timeout of application server.
(1) Access the process that discards session from Web browser.
When using Spring Security, the logout process provided by it is used to discard the session.
For details on logout process of Spring Security, refer to Authentication.
(2) Web application discards HttpSession object corresponding to the session ID linked from
Web browser.
At this point, HttpSession object with the ID, "SESSION01", disappears from server side.
(3) When the discarded session is accessed from Web browser using the respective session ID, a
new session is generated, as HttpSession object corresponding to the requested session ID
does not exist.
In the above example, a session with ID "SESSION02" is being generated.
The scenario in which a session is automatically discarded due to timeout is illustrated below.
(1) When an established session is not accessed for a particular period, application server detects
session timeout.
(2) Application server discards the session for which session timeout is detected.
(3) When the session is accessed from Web browser after session timeout occurs, session timeout
error is returned to Web browser since HttpSession object corresponding to the session ID
requested from the Web browser, does not exist.
When data needs to be stored in the session, the design should include ‘session timeout’. It is recommended
to set the timeout as short as possible, especially when the data to be stored is large.
When Web application is created by the method recommended in this guideline, a request subsequent to session
timeout is detected by any one of the following processes.
1.
Session timeout check process provided by Spring Security.
Session timeout check is performed as per default settings in Spring Security.
Therefore, to store data in session, settings to validate the timeout check process of a Spring
Security session, are required.
For details on timeout check process in Spring Security, refer to How to use.
2.
When not using Spring Security, timeout check process needs to be implemented in Servlet
Filter or HandlerInterceptorof Spring MVC.
The scenario in which session timeout is detected using session check process provided by Spring Security, is
illustrated below.
(1) When an established session is not accessed for a particular period, application server detects
session timeout and discards the session.
(2) Web browser accesses the session after the session timeout occurs.
(3) Spring Security throws session timeout error as, the HttpSession object corresponding to
the session ID linked from client, does not exist.
In default Spring Security implementation, response is sent to the request for redirecting to
URL to display the error screen.
For the processes having precondition, “Data should be stored in session”, session timeout check should
always be performed. If this check is not performed, unexpected system errors and operations may occur,
as data required by the process cannot be fetched.
When data needs to be shared across multiple screens (multiple requests), it can be shared easily by storing this
data in session.
However, as against the advantage that the data can be easily shared, storing the data in session also results in
application constraints etc. Hence, whether or not to use session should be decided by considering application
and system requirements.
Note: Rather than simply storing the data in session, this guideline initially recommends to consider a
policy wherein session is not used. Further, if session is used, it recommends storing only the absolutely
required data in it.
Note: Storing the data applicable to following conditions has proved better.
• Data that does not provide linkage between use cases, but for such data, the status when it is moved
and returned from a different use case needs to be stored.
• Advantages
– Data can be shared across multiple screens (multiple requests) easily when a single process is
composed in multiple screens such as a Wizard Screens.
– By storing the fetched data in session, number of executions of data acquisition can be reduced.
• Disadvantages
– When the screen with same process is opened in multiple browsers or various tabs, data consistency
cannot be maintained, as mutual operations interfere with the data stored in session.
In order to maintain data consistency, control is required so that screen with the same process cannot
be opened in multiple browsers or tabs.
This control can be implemented by using Transaction token check provided by common library,
however it results in reduced usability.
– Session data is usually stored in application server memory; hence memory usage also increases with
increase in data stored in the session.
If unnecessary data that is no longer used in a process is left as it is, it gets excluded from garbage
collection process, leading to memory exhaustion. Such data needs to be deleted from session at a
stage when it is rendered unnecessary.
The timing to delete unnecessary data from session needs to be designed separately.
Note: Any one of the following methods is required to scale out the application server.
1. Performing session replication and sharing the session information with all application servers.
When performing session replication, the load on replication process increases in proportion to
the data stored in session and number of application servers for replication.
Therefore, issues owing to scale out such as, possible degradation in response time etc. need to
be considered.
2. Distributing all requests of the same session to the same application server, using load balancer.
When requests are distributed to the same application server and if the server is down, the
process cannot be continued by another application server.
Therefore, it needs to be noted that, this method may not be feasible in applications that demand
high availability (service level).
Scale out method should be determined upon considering each of these points.
In order to avoid the disadvantages faced while using session, all the data required for server processing can be
implemented by linking as request parameters.
The advantages and disadvantages of not using session are as follows:
• Advantages
– As data is not stored at server side, even if multiple browsers and tabs are used, their operations do
not interfere with each other. Therefore, multiple screens of the same process can be started without
impairing the usability.
– As data is not stored at server side, continuous utilization of memory can be controlled.
– Number of factors that lower the scalability of application server are reduced.
• Disadvantages
– Data required for server processing needs to be sent as request parameter. As a result, even the items
that are not displayed on the screen need to be specified as hidden items.
Thus, JSP implementation code increases.
This can be minimized by creating JSP tag library.
– Amount of data flowing to the network increases, as all the data required for server processing needs
to be sent through requests.
– Data required for screen display needs to be fetched each time. Hence, number of executions of data
acquisition increases.
Serializable objects
Data to be stored in the session may be input or output to disk or network under specific conditions.
Therefore the objects need to be serializable.
• When application server is stopped while a session is active, the session and the data stored in it are saved
to the disk.
The saved session and data stored in it, are restored with the start of application server.
Support status for this data restoring operation differs depending on application server.
• If session storage area is about to overflow or if the session is not accessed for a particular period of time
since the last access, there is a possibility of session swap-out.
The swapped-out session is swapped-in when the session is accessed again.
Conditions etc. for swap-out differ depending on application server.
• To perform replication of session in another application server, data stored in the session is sent to that
application server via network.
When large amount of data is stored in session, it fatally degrades the performance. Hence, it is recommended to
design such that, large amount of data will not be stored in session.
• When storing large amount of data in session, application server settings need to be enabled such that
session is easily swapped-out so as to prevent memory exhaustion.
Swap-out is a “Heavy” process. Hence, it occurring very frequently may affect the overall application
performance.
Support status for swap-out operation or setting method, differs depending on application server.
• When carrying out session replication, serialization and deserialization of an object are performed.
Serialization and deserialization of an object with large capacity being “heavy” processes, may affect
performance, such as response time.
To make the session data compact, storing the data applicable to the following conditions in request scope instead
of session scope, should be considered.
• Data that can be edited by screen operations. It can be shared only in the screen operations of a use case.
If data can be shared in all screen transitions as hidden HTML fields, it need not be stored in session.
A normal system is rarely composed of a single application server. Considering requirements such as availability,
efficiency etc. it consists of multiple servers.
Therefore, when storing the data in session, any one of the following mechanisms needs to be applied in
accordance with system requirements.
1. For systems that require high availability (service level), if one AP server is down, it should be possible to
continue the processing on another AP server.
To be able to continue processing on another AP server when one AP server is down, session information
needs to be shared amongst all AP servers. Hence, session replication needs to be carried out by
application servers that are configured as a cluster.
As an alternate method, session information can be shared by setting the session storage location to cache
server such as Oracle Coherence or database.
It would be better to consider setting the session storage location to a cache server like Oracle Coherence
or database if, number of AP servers, amount of data stored in session and number of sessions that can be
pasted simultaneously are in large quantity.
2. For systems that do not require high availability (service level), processing need not be continued on
another server if the AP server is down.
Therefore, session information need not be shared amongst all AP servers. Thus, it is alright if all the
requests in a same session are distributed to the same AP server, using load balancer functionality.
Warning: When the Web application is created by methods recommended in this guideline, either of the
above mechanisms needs to be applied to store the following data in session.
• User information authenticated by the Spring Security authentication process.
• Token values issued by Spring Security CSRF token check.
• Token values issued by the transaction token check provided by common library.
The session storage location can also be set in the In-memory data grids such as Key-Value Store or Oracle
Coherence, other than AP server memory.
There is room for consideration when scalability is required.
The implementation method to change the storage location of session differs according to the AP server and the
storage location itself. Therefore, it is omitted in this guideline.
This guideline recommends using any one of the following methods to store data in session.
Warning: HttpSession API can be called directly by specifying the HttpSession object as an argu-
ment of Controller handler method. However, as a rule, this guideline strongly recommends not to use the
HttpSession API directly.
HttpSession API may be used directly only for those processes that cannot be implemented otherwise.
However, in most of the business processes, it is not necessary to use the HttpSession API directly. Therefore,
set such that HttpSession object is not specified as an argument of the Controller handler method.
@SessionAttributes annotation is used for sharing data during screen transitions in Controller.
However, cases where each of the screens namely, the input screen, confirmation screen and completion screen
consists of one page, it is better to share the data using ‘hidden’ HTML field rather than using session.
Cases where the input screen consists of multiple pages or when complicated screen transitions are involved,
adopting the method, to store form object in session using @SessionAttributes annotation, should be
considered.
Application designing and implementation can be simplified by storing the form object in session.
@Controller
@RequestMapping("wizard")
@SessionAttributes(types = { WizardForm.class, Entity.class }) // (1)
public class WizardController {
// ...
}
Life cycles of the objects stored in session using @SessionAttributes annotation, are managed at
Controller level.
When setComplete method of SessionStatus object is called, all the objects specified with ‘‘ @Ses-
sionAttributes ‘‘ annotation are deleted from session. Therefore, to store the objects having different life
cycles in session, it is necessary to divide the Controller.
@Controller
@RequestMapping("wizard")
@SessionAttributes(value = { "wizardCreateForm" }) // (2)
public class WizardController {
// ...
@ModelAttribute(value = "wizardCreateForm")
public WizardForm setUpWizardForm() {
return new WizardForm();
}
// ...
}
(2) Specify the attribute name of the object to be stored in session, in “value” attribute of
@SessionAttributes annotation.
Among the objects added to Model object using @ModelAttribute annotation or
addAttribute method of Model, the objects matching with the attribute name specified in
value attribute, are stored in session.
In the above example, objects with attribute name "wizardCreateForm" are stored in
session.
• Method with @ModelAttribute annotation is used to return the object to be added to the session.
• addAttribute method of Model object is used to add the object stored in session.
Object added to Model object is stored in session as per the type of @SessionAttributes annotation and
value of “value” attribute. Hence, there is no need to consider the session when implementation is carried out
using handler method of Controller.
How to return the object to be stored in session using the method with @ModelAttribute annotation, is
explained below.
It is recommended to create object using this method when storing form object in session.
(1) In “value” attribute, specify the attribute name to be stored in Model object.
In the above example, the object returned is stored in session, with attribute name
"wizardForm".
When “value” attribute is specified, method with @ModelAttribute annotation is no longer
called by the subsequent requests after the object is stored in session. Thus, it has an advantage
wherein, unnecessary objects are not generated.
@ModelAttribute // (1)
public WizardForm setUpWizardForm() {
return new WizardForm();
}
(1) Use the method with @ModelAttribute annotation to generate and return the
object to be added in session.
In the above example, object returned with attribute name "wizardForm"
annotation, is stored in session.
How to add object to session using addAttribute method of Model object is explained below.
When storing the Domain object to session, this method is used to add the object.
(3) Add the object to be stored in session using addAttribute method of Model object.
In the above example, the object fetched from domain layer with the attribute name "entity"
is stored in session.
The object stored in session can be received as an argument of Controller handler method.
There is no need to consider the session in Controller handler method, as the object stored session gets stored in
Model object as per the attribute value of @SessionAttributes annotation.
(2) In the above example, object stored in session scope with attribute name "entity", is passed
to argument “entity”.
When the object to be passed to the argument of Controller handler method does not exist in Model object, the
operation changes depending on whether @ModelAttribute annotation is specified or not.
• When @ModelAttribute annotation is not specified, a new object is created and passed as argument.
The created object is stored in Model object and subsequently in session as well.
Created object is not stored in session if it is redirected to a transition destination. Therefore, when re-
ferring to the created object in redirect process, it is necessary to store the object in Flash scope using
addFlashAttribute method of RedirectAttributes.
(4) When @ModelAttribute annotation is specified in argument and when the target object
that does not exist in session is called, HttpSessionRequiredException occurs.
HttpSessionRequiredException occurs due to client operations such as browser back,
directly accessing specified URL etc. Therefore, the exception needs to be handled as client
error.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
<!-- ... -->
<property name="exceptionMappings">
<map>
<!-- ... -->
<entry key="HttpSessionRequiredException "
value="common/error/operationError" /> <!-- (5) -->
</map>
</property>
<property name="statusCodes">
<map>
<!-- ... -->
<entry key="common/error/operationError" value="400" /> <!-- (6) -->
</map>
</property>
<!-- ... -->
</bean>
• applicationContext.xml
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<!-- Setting and Customization by project. -->
<property name="exceptionMappings">
<map>
<!-- ... -->
<entry key="HttpSessionRequiredException" value="w.xx.0003" /> <!-- (7) -->
</map>
</property>
<property name="defaultExceptionCode" value="e.xx.0001" /> <!-- (8) -->
</bean>
To delete the object stored in session using @SessionAttributes, call setComplete method of
org.springframework.web.bind.support.SessionStatus from the handler method of
Controller.
On calling setComplete method of SessionStatus object, the object specified in attribute value of
@SessionAttributes annotation, is deleted from session.
By calling setComplete method of SessionStatus object, the object specified in attribute value of
@SessionAttributes annotation is deleted from session. However, the actual time when the object is
deleted is different than when setComplete method is called.
setComplete method of SessionStatus object changes only the internal flag, whereas, the actual dele-
tion of object is carried out by the framework, after the handler method of Controller is completed.
Object is deleted from session by calling the setComplete method of SessionStatus object. However,
it can be referred from View (JSP) as the same object remains in Model object.
The objects stored in session need to be deleted from the following 3 locations.
Example given below illustrates implementation of object deletion by using a request to display completion screen.
// (1)
@RequestMapping(value = "save", method = RequestMethod.POST)
public String save(@ModelAttribute @Validated({ Wizard1.class,
Wizard2.class, Wizard3.class }) WizardForm form,
BindingResult result, Entity entity,
RedirectAttributes redirectAttributes) {
// ...
return "redirect:/wizard/save?complete"; // (2)
}
// (3)
@RequestMapping(value = "save", params = "complete", method = RequestMethod.GET)
public String saveComplete(SessionStatus sessionStatus) {
sessionStatus.setComplete(); // (4)
return "wizard/complete";
}
(4) Call setComplete method of SessionStatus object and delete the object from session.
Display process for View (JSP) is not affected directly as the same object remains in Model
object.
Example given below illustrates implementation of object deletion by using a request to stop consecutive opera-
tions.
// (1)
@RequestMapping(value = "save", params = "cancel", method = RequestMethod.POST)
public String saveCancel(SessionStatus sessionStatus) {
sessionStatus.setComplete(); // (2)
return "redirect:/wizard/menu"; // (3)
}
(2) Call setComplete method of SessionStatus object and delete the object from session.
Example given below illustrates implementation of object deletion by using a request for initial display of input
screen.
// (1)
@RequestMapping(value = "create", method = RequestMethod.GET)
public String initializeCreateWizardForm(SessionStatus sessionStatus) {
sessionStatus.setComplete(); // (2)
return "redirect:/wizard/create?form1"; // (3)
}
// (4)
@RequestMapping(value = "create", params = "form1")
public String createForm1() {
return "wizard/form1";
}
For specific implementation, refer to Example of screen transition implementation using @SessionAttributes in
wizard format of Appendix.
• Class
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) // (1)
public class SessionCart implements Serializable {
(1) Set Bean scope to "session". Also, it be enabled the scoped-proxy by specifying a
ScopedProxyMode.TARGET_CLASS in proxyMode attribute.
(2) Create a method to clear the cart content (delete products from the cart) while order has been
finished.
Note: To define Entity class to be handled using JPA as session scope ban, it is recommended to provide
a wrapper class instead of defining it directly as session-scoped bean.
If Entity class to be handled using JPA is defined as session-scoped bean, it cannot be handled directly
using the API of JPA (throws error if handled directly). Therefore, a process to convert it to Entity object
that can be handled in JPA is required.
In the above example, JPA Entity class called Cart is wrapped in a wrapper class called SessionCart
and set as session-scoped bean. With this, the process to convert it to Entity object that can be handled in
JPA is no longer required; hence the process to be performed in Controller becomes simple.
• spring-mvc.xml
• JavaBean
@Inject
SessionCart sessionCart; // (1)
@RequestMapping(value = "add")
public String addCart(@Validated ItemForm form, BindingResult result) {
if (result.hasErrors()) {
return "item/item";
}
CartItem cartItem = beanMapper.map(form, CartItem.class);
Cart addedCart = cartService.addCartItem(sessionCart.getCart(), // (2)
cartItem);
sessionCart.setCart(addedCart); // (3)
return "redirect:/cart";
}
(2) On calling the method of session-scoped bean, object stored in session is returned.
When there is no object stored in session, newly created object is returned as well as stored in
session.
In the above example, Service method is called to check inventory etc. before adding to cart.
(3) In the above example, Cart object passed as an argument of addCartItem method of
CartService and
the Cart object returned with value, may form a separate instance.
Therefore, the returned Cart object is set to session-scoped bean.
A session-scoped Bean can be referred from JSP even when Bean is not added to Model object in Con-
troller by using SpEL(Spring Expression Language) formula.
</c:forEach>
If you want to remove object from session which is not required any more, reset the Bean field from session
scope.
Note: The IoC Container destroys session-scoped beans when a session expires.
Since the IoC Container manages the lifecycle of session-scoped beans, avoid deleting them explicitly.
@Controller
@RequestMapping("order")
public class OrderController {
@Inject
SessionCart sessionCart; // (1)
// ...
@RequestMapping(method = RequestMethod.POST)
public String order() {
// ...
return "redirect:/order?complete";
}
(2) delete all items which have already been ordered by initializing the state of the session-scoped
bean.
For a more specific implementation, refer to Example of screen transition across multiple Controllers using
session-scoped bean. of Appendix.
Class that outputs the operations performed for a session to debug log, is provided by common library.
The log output by this class is effective to check whether session operations are performed as expected.
To use JSP implicit object sessionScope , the session attribute value of page directive needs to be set to true.
It is set as false in the include.jsp, provided by blank project.
• include.jsp
It is recommended to synchronize the requests in the same session in order to use @SessionAttributes
annotation or session-scoped bean.
If the requests are not synchronized, the object stored in session may be accessed at the same time, causing
unexpected errors or operations.
For example, incorrect value may be set for the form object with completed input validation.
To prevent this, it is strongly recommended to set synchronizeOnSession of
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
to ‘true’ and synchronize the requests in the same session.
• Component
package com.example.app.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// NO-OP
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instance of RequestMappingHandlerAdapter) {
RequestMappingHandlerAdapter adapter =
(RequestMappingHandlerAdapter) bean;
logger.info("enable synchronizeOnSession => {}", adapter);
adapter.setSynchronizeOnSession(true); // (1)
}
return bean;
}
• spring-mvc.xml
4.3.4 Appendix
Screen transition implementation using @SessionAttributes annotation in wizard format, is explained be-
low.
• Input screen consists of 3 sub-screens wherein one field is entered on each sub-screen.
• Before saving (registering/updating), the entered values can be verified on confirmation screen.
• Input validation is performed at the time of screen transition. User is returned to input screen in case of
error.
• Before saving (registering/updating), perform input validation again for all the input values and display the
error screen notifying invalid operations.
• If all the input values are appropriately validated, save the input data to the database.
• Form object
// (1)
@NotEmpty(groups = { Wizard1.class })
private String field1;
// (2)
@NotEmpty(groups = { Wizard2.class })
private String field2;
// (3)
@NotEmpty(groups = { Wizard3.class })
private String field3;
// ...
// (4)
public static interface Wizard1 {
}
// (5)
public static interface Wizard2 {
}
// (6)
public static interface Wizard3 {
(4) Verification group interface to indicate that it is the field entered on the first page of input
screen.
(5) Verification group interface to indicate that it is the field entered on the second page of input
screen.
(6) Verification group interface to indicate that it is the field entered on the third page of input
screen.
When performing input validation for screen transition, only the fields on corresponding page need to be
validated. In Bean Validation, verification rules can be grouped by setting the interface or class representing
the verification group. In this implementation, input validation for each screen is performed by providing
verification group for each screen.
• Controller
@Controller
@RequestMapping("wizard")
@SessionAttributes(types = { WizardForm.class, Entity.class }) // (7)
public class WizardController {
@Inject
WizardService wizardService;
@Inject
Mapper beanMapper;
(7) In the above example, form object (WizardForm.class) and entity (Entity.class)
object are stored in session.
@ModelAttribute("wizardForm") // (8)
public WizardForm setUpWizardForm() {
return new WizardForm();
}
(8) In the above example, form object (WizardForm) to be stored in session is generated. To
prevent creation of unnecessary objects, “value” attribute of @ModelAttribute annotation
is specified.
// (9)
@RequestMapping(value = "create", method = RequestMethod.GET)
public String initializeCreateWizardForm(SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "redirect:/wizard/create?form1";
}
// (10)
@RequestMapping(value = "create", params = "form1")
public String createForm1() {
return "wizard/form1";
}
(9) Handler method for initial display of input screen for registration.
Objects for which operation is in process, may be stored in session; hence such objects are
deleted by this handler method.
(10) Handler method to display the first page of input screen for registration.
// (11)
@RequestMapping(value = "{id}/update", method = RequestMethod.GET)
public String initializeUpdateWizardForm(@PathVariable("id") Integer id,
RedirectAttributes redirectAttributes, SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "redirect:/wizard/{id}/update?form1";
}
// (12)
@RequestMapping(value = "{id}/update", params = "form1")
public String updateForm1(@PathVariable("id") Integer id, WizardForm form,
Model model) {
Entity loadedEntity = wizardService.getEntity(id);
beanMapper.map(loadedEntity, form); // (13)
model.addAttribute(loadedEntity); // (14)
return "wizard/form1";
}
(11) Handler method for initial display of input screen for update.
(12) Handler method to display input screen for update (first page).
(13) Set the fetched entity status in form object. In the above example, Bean mapper library called
Dozer, is used.
(14) Add the fetched entity to Model object and store in session.
In the above example, it is stored in session with attribute name "entity".
// (15)
@RequestMapping(value = "save", params = "form2", method = RequestMethod.POST)
public String saveForm2(@Validated(Wizard1.class) WizardForm form, // (16)
BindingResult result) {
if (result.hasErrors()) {
return saveRedoForm1();
}
return "wizard/form2";
}
// (17)
@RequestMapping(value = "save", params = "form3", method = RequestMethod.POST)
// (19)
@RequestMapping(value = "save", params = "confirm", method = RequestMethod.POST)
public String saveConfirm(@Validated(Wizard3.class) WizardForm form, // (20)
BindingResult result) {
if (result.hasErrors()) {
return saveRedoForm3();
}
return "wizard/confirm";
}
(16) Specify the verification group (Wizard1.class) on the first page of input screen, in “value”
attribute of @Validated annotation, so as to perform input validation of only the value
entered in first page of input screen.
(18) Specify the verification group (Wizard2.class) on the second page of input screen, in
“value” attribute of @Validated annotation, so as to perform input validation of only the
value entered in second page of input screen.
(20) Specify the verification group (Wizard3.class) on the third page of input screen, in “value”
attribute of @Validated annotation, so as to perform input validation of only the value
entered in third page of input screen.
// (21)
@RequestMapping(value = "save", method = RequestMethod.POST)
public String save(@ModelAttribute @Validated({ Wizard1.class,
Wizard2.class, Wizard3.class }) WizardForm form, // (22)
BindingResult result,
Entity entity, // (23)
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
throw new InvalidRequestException(result); // (24)
}
beanMapper.map(form, entity);
redirectAttributes.addFlashAttribute(entity); // (26)
return "redirect:/wizard/save?complete";
}
// (27)
@RequestMapping(value = "save", params = "complete", method = RequestMethod.GET)
public String saveComplete(SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "wizard/complete";
}
(24) As error does not occur when screen transition is performed using the buttons provided by the
application, InvalidRequestException is thrown when an invalid operation is
performed.
Further, exception class InvalidRequestException needs to be created separately, as it
is not provided by common library.
(26) Entity.class object saved by handler method of redirect destination is stored in Flash
scope so that it can be referred.
// (28)
@RequestMapping(value = "save", params = "redoForm1")
public String saveRedoForm1() {
return "wizard/form1";
}
// (29)
@RequestMapping(value = "save", params = "redoForm2")
// (30)
@RequestMapping(value = "save", params = "redoForm3")
public String saveRedoForm3() {
return "wizard/form3";
}
@Controller
@RequestMapping("wizard")
@SessionAttributes(types = { WizardForm.class, Entity.class })
// (7)
public class WizardController {
@Inject
EntityService wizardService;
@Inject
Mapper beanMapper;
@ModelAttribute("wizardForm")
// (8)
public WizardForm setUpWizardForm() {
return new WizardForm();
}
// (9)
@RequestMapping(value = "create", method = RequestMethod.GET)
public String initializeCreateWizardForm(SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "redirect:/wizard/create?form1";
// (10)
@RequestMapping(value = "create", params = "form1")
public String createForm1() {
return "wizard/form1";
}
// (11)
@RequestMapping(value = "{id}/update", method = RequestMethod.GET)
public String initializeUpdateWizardForm(@PathVariable("id") Integer id,
RedirectAttributes redirectAttributes, SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "redirect:/wizard/{id}/update?form1";
}
// (12)
@RequestMapping(value = "{id}/update", params = "form1")
public String updateForm1(@PathVariable("id") Integer id, WizardForm form,
Model model) {
Entity loadedEntity = wizardService.getEntity(id);
beanMapper.map(loadedEntity, form); // (13)
model.addAttribute(loadedEntity); // (14)
return "wizard/form1";
}
// (15)
@RequestMapping(value = "save", params = "form2", method = RequestMethod.POST)
public String saveForm2(@Validated(Wizard1.class) WizardForm form, // (16)
BindingResult result) {
if (result.hasErrors()) {
return saveRedoForm1();
}
return "wizard/form2";
}
// (17)
@RequestMapping(value = "save", params = "form3", method = RequestMethod.POST)
public String saveForm3(@Validated(Wizard2.class) WizardForm form, // (18)
BindingResult result) {
if (result.hasErrors()) {
return saveRedoForm2();
}
return "wizard/form3";
}
// (19)
@RequestMapping(value = "save", params = "confirm", method = RequestMethod.POST)
public String saveConfirm(@Validated(Wizard3.class) WizardForm form, // (20)
BindingResult result) {
if (result.hasErrors()) {
return saveRedoForm3();
}
return "wizard/confirm";
}
// (21)
@RequestMapping(value = "save", method = RequestMethod.POST)
public String save(@ModelAttribute @Validated({ Wizard1.class,
Wizard2.class, Wizard3.class }) WizardForm form, // (22)
BindingResult result, Entity entity, // (23)
RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
throw new InvalidRequestException(result); // (24)
}
beanMapper.map(form, entity);
redirectAttributes.addFlashAttribute(entity); // (26)
return "redirect:/wizard/save?complete";
}
// (27)
@RequestMapping(value = "save", params = "complete", method = RequestMethod.GET)
public String saveComplete(SessionStatus sessionStatus) {
sessionStatus.setComplete();
return "wizard/complete";
}
// (28)
@RequestMapping(value = "save", params = "redoForm1")
public String saveRedoForm1() {
return "wizard/form1";
}
// (29)
@RequestMapping(value = "save", params = "redoForm2")
public String saveRedoForm2() {
return "wizard/form2";
}
// (30)
@RequestMapping(value = "save", params = "redoForm3")
public String saveRedoForm3() {
return "wizard/form3";
}
<html>
<head>
<title>Wizard Form(1/3)</title>
</head>
<body>
<h1>Wizard Form(1/3)</h1>
<form:form action="${pageContext.request.contextPath}/wizard/save"
modelAttribute="wizardForm">
<form:label path="field1">Field1</form:label> :
<form:input path="field1" />
<form:errors path="field1" />
<div>
<form:button name="form2">Next</form:button>
</div>
</form:form>
</body>
</html>
<html>
<head>
<title>Wizard Form(2/3)</title>
</head>
<body>
<h1>Wizard Form(2/3)</h1>
<%-- (31) --%>
<form:form action="${pageContext.request.contextPath}/wizard/save"
modelAttribute="wizardForm">
<form:label path="field2">Field2</form:label> :
<form:input path="field2" />
<form:errors path="field2" />
<div>
<form:button name="redoForm1">Back</form:button>
<form:button name="form3">Next</form:button>
</div>
</form:form>
</body>
</html>
(31) There is no need to hide the input screen fields of first page since the form object is stored in
session.
<html>
<head>
<title>Wizard Form(3/3)</title>
</head>
<body>
<h1>Wizard Form(3/3)</h1>
<%-- (32) --%>
<form:form action="${pageContext.request.contextPath}/wizard/save"
modelAttribute="wizardForm">
<form:label path="field3">Field3</form:label> :
<form:input path="field3" />
<form:errors path="field3" />
<div>
<form:button name="redoForm2">Back</form:button>
<form:button name="confirm">Confirm</form:button>
</div>
</form:form>
</body>
</html>
(32) There is no need to hide the input screen fields of first and second page since form object is
stored in session.
<html>
<head>
<title>Confirm</title>
</head>
<body>
<h1>Confirm</h1>
<%-- (33) --%>
<form:form action="${pageContext.request.contextPath}/wizard/save"
modelAttribute="wizardForm">
<div>
Field1 : <c:out value="${wizardForm.field1}" />
</div>
<div>
Field2 : <c:out value="${wizardForm.field2}" />
</div>
<div>
Field3 : <c:out value="${wizardForm.field3}" />
</div>
<div>
<form:button name="redoForm3">Back</form:button>
<form:button>OK</form:button>
</div>
</form:form>
</body>
</html>
(33) There is no need to hide input screen fields since form object is stored in session.
<html>
<head>
<title>Complete</title>
</head>
<body>
<h1>Complete</h1>
<div>
<div>
ID : ${f:h(entity.id)}
</div>
<div>
Field1 : ${f:h(entity.field1)}
</div>
<div>
Field2 : ${f:h(entity.field2)}
</div>
<div>
Field3 : ${f:h(entity.field3)}
</div>
</div>
<div>
<a href="${pageContext.request.contextPath}/wizard/create">
Continue operation of Create
</a>
</div>
<div>
<a href="${pageContext.request.contextPath}/wizard/${entity.id}/update">
Continue operation of Update
</a>
</div>
</body>
</html>
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
<!-- ... -->
<property name="exceptionMappings">
<map>
<!-- ... -->
<entry key="InvalidRequestException"
value="common/error/operationError" /> <!-- (34) -->
</map>
</property>
<property name="statusCodes">
<map>
<!-- ... -->
<entry key="common/error/operationError" value="400" /> <!-- (35) -->
</map>
</property>
<!-- ... -->
</bean>
• applicationContext.xml
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<!-- Setting and Customization by project. -->
<property name="exceptionMappings">
<map>
<!-- ... -->
<entry key="InvalidRequestException" value="w.xx.0004" /> <!-- (36) -->
</map>
</property>
<property name="defaultExceptionCode" value="e.xx.0001" /> <!-- (37) -->
</bean>
Implementation using session-scoped bean is explained with example of process of performing screen transitions
across multiple Controllers.
• Above 3 processes are provided as independent functions and should be set in separate Controllers (Item-
Controller, CartController, OrderController).
Implementation is as follows:
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SessionCart implements Serializable {
(2) Make cart empty by removing all product objects from cartwhich are added in the cart.
• ItemController
@Controller
@RequestMapping("item")
public class ItemController {
@Inject
SessionCart sessionCart;
@Inject
CartService cartService;
@Inject
Mapper beanMapper;
@ModelAttribute
public ItemForm setUpItemForm() {
return new ItemForm();
}
// (3)
@RequestMapping
public String view(Model model) {
return "item/item";
}
// (4)
@RequestMapping(value = "add")
public String addCart(@Validated ItemForm form, BindingResult result) {
if (result.hasErrors()) {
return "item/item";
}
CartItem cartItem = beanMapper.map(form, CartItem.class);
Cart cart = cartService.addCartItem(sessionCart.getCart(), // (5)
cartItem);
sessionCart.setCart(cart); // (6)
return "redirect:/cart"; // (7)
}
}
(7) Redirect to the request to display cart screen after adding products to cart.
When transiting to a separate Controller screen, instead of calling View (JSP) directly, it
is recommended to redirect to the request for displaying the screen.
• CartController
@Controller
@RequestMapping("cart")
public class CartController {
@Inject
SessionCart sessionCart;
@Inject
CartService cartService;
@Inject
Mapper beanMapper;
@ModelAttribute
public CartForm setUpCartForm() {
return new CartForm();
}
// (8)
@RequestMapping
public String cart(CartForm form) {
beanMapper.map(sessionCart.getCart(), form);
return "cart/cart";
}
// (9)
@RequestMapping(params = "edit", method = RequestMethod.POST)
public String edit(@Validated CartForm form, BindingResult result,
Model model) {
if (result.hasErrors()) {
return "cart/cart";
}
cart = cartService.saveCart(cart);
sessionCart.setCart(cart); // (10)
(11) Redirect to the request to display cart screen (quantity change screen) after changing quantity.
In case of Update process, instead of calling View (JSP) directly, it is recommended to
redirect to the request for displaying the screen.
• OrderController
@Controller
@RequestMapping("order")
@SessionAttributes("scopedTarget.sessionCart")
public class OrderController {
@Inject
SessionCart sessionCart;
@ModelAttribute
public OrderForm setUpOrderForm() {
return new OrderForm();
}
// (12)
@RequestMapping
public String view() {
return "order/order";
}
// (13)
@RequestMapping(method = RequestMethod.POST)
public String order() {
// ...
return "redirect:/order?complete";
}
// (14)
@RequestMapping(params = "complete", method = RequestMethod.GET)
public String complete(Model model, SessionStatus sessionStatus) {
sessionCart.clearCart();
return "order/complete";
}
<html>
<head>
<title>Item</title>
</head>
<body>
<h1>Item</h1>
<form:form action="${pageContext.request.contextPath}/item/add"
modelAttribute="itemForm">
<form:label path="itemCode">Item Code</form:label> :
<form:input path="itemCode" />
<form:errors path="quantity" />
<br>
<form:label path="quantity">Quantity</form:label> :
<form:input path="quantity" />
<form:errors path="quantity" />
<div>
<%-- (15) --%>
<form:button>Add</form:button>
</div>
</form:form>
<div>
<a href="${pageContext.request.contextPath}/cart">Go to Cart</a>
</div>
</body>
</html>
<html>
<head>
<title>Cart</title>
</head>
<body>
<%-- (16) --%>
<spring:eval var="cart" experssion="@sessionCart.cart" />
<h1>Cart</h1>
<c:choose>
<c:when test="${ empty cart.cartItems }">
<div>Cart is empty.</div>
</c:when>
<c:otherwise>
CART ID :
${f:h(cart.id)}
<form:form modelAttribute="cartForm">
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>ITEM CODE</th>
<th>QUANTITY</th>
</tr>
</thead>
<tbody>
<c:forEach var="item"
items="${cart.cartItems}"
varStatus="rowStatus">
<tr>
<td>${f:h(item.id)}</td>
<td>${f:h(item.itemCode)}</td>
<td>
<form:input
path="cartItems[${rowStatus.index}].quantity" />
<form:errors
path="cartItems[${rowStatus.index}].quantity" />
</td>
</tr>
</c:forEach>
</tbody>
</table>
<%-- (17) --%>
<form:button name="edit">Save</form:button>
</form:form>
</c:otherwise>
</c:choose>
<c:if test="${ not empty cart.cartItems }">
<div>
<%-- (18) --%>
<a href="${pageContext.request.contextPath}/order">Go to Order</a>
</div>
</c:if>
<div>
<a href="${pageContext.request.contextPath}/item">Back to Item</a>
</div>
</body>
</html>
<html>
<head>
<title>Order</title>
</head>
<body>
<spring:eval var="cart" experssion="@sessionCart.cart" />
<h1>Order</h1>
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>ITEM CODE</th>
<th>QUANTITY</th>
</tr>
</thead>
<tbody>
<c:forEach var="item" items="${cart.cartItems}"
varStatus="rowStatus">
<tr>
<td>${f:h(item.id)}</td>
<td>${f:h(item.itemCode)}</td>
<td>${f:h(item.quantity)}</td>
</tr>
</c:forEach>
</tbody>
</table>
<form:form modelAttribute="orderForm">
<%-- (19) --%>
<form:button>Order</form:button>
</form:form>
<div>
<a href="${pageContext.request.contextPath}/cart">Back to Cart</a>
</div>
<div>
<a href="${pageContext.request.contextPath}/item">Back to Item</a>
</div>
</body>
</html>
<html>
<head>
<title>Order Complete</title>
</head>
<body>
<h1>Order Complete</h1>
ORDER ID :
${f:h(order.id)}
<table border="1">
<thead>
<tr>
<th>ID</th>
<th>ITEM CODE</th>
<th>QUANTITY</th>
</tr>
</thead>
<tbody>
<c:forEach var="item" items="${order.orderItems}"
varStatus="rowStatus">
<tr>
<td>${f:h(item.id)}</td>
<td>${f:h(item.itemCode)}</td>
<td>${f:h(item.quantity)}</td>
</tr>
</c:forEach>
</tbody>
</table>
<br>
<div>
<a href="${pageContext.request.contextPath}/item">Back to Item</a>
</div>
</body>
</html>
4.4 Pagination
4.4.1 Overview
This chapter describes the Pagination functionality wherein the data matching the search criteria is divided into
pages and displayed.
It is recommended to use pagination functionality when a large amount of data matches the search criteria.
Retrieving and displaying a large amount of data at a time on screen may lead to following problems.
• Network load
Transferring unnecessary data over the network results in increased network load and thereby may affect
the response time of the entire system.
(2) Display the information related to pagination (total records, total pages and number of
displayed pages etc.).
Tag library to display this area does not exist; hence it should be implemented separately as JSP
processing.
Page search
For implementing pagination, it is essential to first implement the server-side search processing to make page
searching possible.
It is assumed in this guideline that the mechanism provided by Spring Data is used for page search at server side.
Extract the information required for page search (location of page to be searched, number of
records to be fetched and sort condition) from request parameter and pass the extracted
information as objects of org.springframework.data.domain.Pageable to the
argument of Controller.
This functionality is provided as
org.springframework.data.web.PageableHandlerMethodArgumentResolver
class and is enabled by adding to <mvc:argument-resolvers> element of
spring-mvc.xml .
For request parameters, refer to “Note column”.
Save the page information (total records, data of corresponding page, location of page to be
searched, number of records to be fetched and sort condition).
This functionality is provided as org.springframework.data.domain.Page
interface and org.springframework.data.domain.PageImpl is provided as default
implementation class.
As per specifications, it fetches the required data from Page object in JSP tag library to
output pagination link provided by common library.
When Spring Data JPA is used for database access, the information of corresponding page is
returned as Page object by specifying Pageable object as an argument of Repository Query
method.
All the processes such as executing SQL to fetch total records, adding sort condition and
extracting data matching the corresponding page are carried out automatically.
When MyBatis is used for database access, the process that is automatically carried out in
Spring Data JPA needs to be implemented in Java (Service) and SQL mapping file.
Request parameters for page search provided by Spring Data are as follows:
size
2.
Request parameter to specify the count of fetched records.
Specify value greater than or equal to 1.
When value specified is greater than the value in maxPageSize of
PageableHandlerMethodArgumentResolver, maxPageSize
value becomes size value.
sort
3.
Parameter to specify sort condition (multiple parameters can be specified).
Specify the value in "{sort item name(,sort order)}" format.
Specify either "ASC" or "DESC" as sort order. When nothing is specified,
"ASC" is used.
Multiple item names can be specified using "," separator.
For example, when
"sort=lastModifiedDate,id,DESC&sort=subId" is specified
as query string, Order By clause "ORDER BY lastModifiedDate
DESC, id DESC, subId ASC" is added to the query.
Warning: About operations when invalid values are specified in request parameters of spring-
data-commons 1.6.1.RELEASE
spring-data-commons 1.6.1.RELEASE having terasoluna-gfw-common 1.0.0.RELEASE
has a bug wherein if an invalid value is specified in request parameters for page
search (page, size, sort etc.), java.lang.IllegalArgumentException or
java.lang.ArrayIndexOutOfBoundsException occurs and SpringMVC settings when set
to default values leads to system error (HTTP status code=500).
This problem is handled using JIRA DATACMNS-379 and DATACMNS-408 and is being resolved in
spring-data-commons 1.6.3.RELEASE. Post modification, if invalid values are specified, the default
value when parameters are omitted will be applied.
In cases where terasoluna-gfw-common 1.0.0.RELEASE is used, the version should be upgraded to
terasoluna-gfw-common 1.0.1.RELEASE or higher version.
Note: Points to be noted due to the changes in API specifications of Spring Data Commons
Specifically,
• In Page interface and PageImpl class, isFirst() and isLast() methods are added in spring-
data-commons 1.8.0.RELEASE, and isFirstPage() and isLastPage() methods are deleted
from spring-data-commons 1.9.0.RELEASE.
If deleted API is used, there will be a compilation error and application will have to be modified. In
addition, when using Page interface (PageImpl class) as resource object of REST API, that application
may also need to be modified, as JSON and XML format get changed.
This section describes the pagination link which is output using JSP tag library of common library.
Style sheet to display pagination link is not provided from common library, hence it should be created in each
project.
Bootstrap v3.0.0 style sheet is applied for the screens used in the explanation below.
(6) Status indicating link where operations cannot be performed on the currently displayed page.
The status is specifically “Link to navigate to the first page” and “Link to navigate to the
previous page” when the first page is displayed and “Link to navigate to the next page” “Link to
navigate to the last page” when the last page is displayed.
This status is defined as "disabled" in the JSP tag library of common library.
• JSP
• HTML to be output
• HTML
• Screen image
(7) Specify the text to be displayed for page navigation link Refer to the
In common library, this part is called “Page Link Text”. following ” Note
column ”.
As per default setting, there are maximum 14 “Inner Elements”. Their division is as follows:
The number of “Inner Elements” can be changed by specifying parameters of JSP tag library.
As per default setting, following are the three values depending on location of the page.
• "disabled" : Style class indicating the link which cannot be operated on the currently displayed
page.
"disabled" and "active" values can be changed by specifying the parameters of JSP tag library.
When link status is "disabled" and "active", the default value is "javascript:void(0)" and
in other cases, the default value is "?page={page}&size={size}".
“Page Link URL” can be changed to another value by specifying parameters of JSP tag library.
Links other than “Link to navigate to the specified page” can be changed as per the specification of param-
eters of JSP tag library.
Default operations can be changed by specifying values in parameters of JSP tag library.
outerElementClass
2.
Specify class name of style sheet to be set in “Outer Element Class”.
Example: pagination
innerElement
3.
Specify HTML element name to be used as “Inner Element”.
Example: span
disabledClass
4.
Specify the value to be set in the class attribute of “Inner Element”
with "disabled" status.
Example: hiddenPageLink
activeClass
5.
Specify the value to be set in the class attribute of “Inner Element”
with "active" status.
Example: currentPageLink
firstLinkText
6.
Specify the value to be set in “Page Link Text” of “Link to navigate
to the first page”.
If "" is specified, “Link to navigate to the first page” itself is not
output.
Example: First
previousLinkText
7.
Specify the value to be set in “Page Link Text” of “Link to navigate
to the previous page”.
If "" is specified, “Link to navigate to the previous page” itself is not
output.
Example: Prev
nextLinkText
8.
4.4. Pagination Specify the value to be set in “Page Link Text” of “Link to navigate
531
to the next page”.
If "" is specified, “Link to navigate to the next page” itself is not
output.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When default values of all parameters to control the layout are changed, the following HTML is output.
The numbers in figure correspond to serial numbers in the parameter list mentioned above.
• JSP
<t:pagination page="${page}"
outerElement="div"
outerElementClass="pagination"
innerElement="span"
disabledClass="hiddenPageLink"
activeClass="currentPageLink"
firstLinkText="First"
previousLinkText="Prev"
nextLinkText="Next"
lastLinkText="Last"
maxDisplayCount="5"
/>
• Output HTML
pathTmpl
2.
Specify the template of request path to be set in “Page Link URL”.
When request path at the time of page display and the request path
for page navigation are different, request path for page navigation
needs to be specified in this parameter.
In the template of request path to be specified, location of page
(page) and number of records to be fetched (size) can be specified as
path variables (placeholders).
The specified value of URL is encoded in UTF-8.
queryTmpl
3.
Specify the template of query string of “Page Link URL”.
Specify the template for generating pagination related query string
(page, size, sort parameters) required at the time of page navigation.
When setting request parameter name for location of page or the
number of records to be fetched to values other than default values,
the query string needs to be specified in this parameter.
In the template of query string to be specified, location of page
(page) and number of records to be fetched (size) can be specified as
path variables (placeholders).
The specified value of URL is encoded in UTF-8.
criteriaQuery
4.
Specify the query string for search conditions to be added to “Page
Link URL”.
Specify the query string for search conditions in this parameter
when adding search conditions to “Page Link URL”.
The specified value of URL is not encoded; hence encoded URL
query string needs to be specified.
However, if the focus is moved or on mouseover to page link in default state, "javascript:void(0)"
may be displayed on browser status bar. To change this behavior, it is necessary to disable the operation of
page link click by using JavaScript. Refer to “To disable page link using JavaScript” for implementation
example.
Path variables that can be specified in pathTmpl and queryTmpl are as follows:
When parameters to control the operations are changed, the following HTML is output. The numbers in
figure correspond to serial numbers of parameter list mentioned above.
• JSP
<t:pagination page="${page}"
disabledHref="#"
pathTmpl="${pageContext.request.contextPath}/article/list/{page}/{size}"
queryTmpl="sort={sortOrderProperty},{sortOrderDirection}"
criteriaQuery="${f:query(articleSearchCriteriaForm)}"
enableLinkOfCurrentPage="true" />
• HTML to be output
Process flow when using pagination functionality of Spring Data and JSP tag library of common library is as
follows:
(1) Apart from search conditions, specify the location of page to be searched (page) and number of
records to be fetched (size) as request parameters and send the request.
(3) Controller passes the Pageable object received as an argument to Service method.
The implementation method of (5) & (6) are different depending on the O/R Mapper to be used.
• When using MyBatis3, implementation of Java (Service) and SQL mapping file is necessary.
• When using Spring Data JPA, implementation is not necessary because it is carried out automatically
through the use of Spring Data JPA functionality.
Application settings
Location of page to be searched (page), number of records to be fetched (size) and sort condition (sort) are
specified in the request parameter. The functionality to set these properties in the argument of Controller as
Pageable object should be enabled.
The settings mentioned below are preset in a blank project.
spring-mvc.xml
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- (1) -->
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
</mvc:argument-resolvers>
</mvc:annotation-driven>
(1) Specify
org.springframework.data.web.PageableHandlerMethodArgumentResolver
in <mvc:argument-resolvers>.
For the properties that can be specified in
PageableHandlerMethodArgumentResolver, refer to “About property values of
PageableHandlerMethodArgumentResolver”.
Page search
The information required for page search (such as location of page to be searched, number of records to be fetched
and sort condition) is received as an argument and passed to Service method.
• Controller
@RequestMapping("list")
public String list(@Validated ArticleSearchCriteriaForm form,
BindingResult result,
Pageable pageable, // (1)
Model model) {
return "article/list";
}
(2) Specify the Pageable object as an argument of Service method and then call the same.
(3) Add the search result (Page object) returned by Service to Model. It can be referred from
View (JSP) after it is added to Model.
Note: Operations when the information required for page search is not specified in request param-
eter
Default values are applied when the information required for page search (such as location of page to be
searched, number of records to be fetched and sort condition) is not specified in request parameter. Default
values are as follows:
• Specify Pageable object wherein default values are defined in fallbackPageable property of
PageableHandlerMethodArgumentResolver.
See the method below for specifying default values using @PageableDefault annotation.
Use @PageableDefault annotation to change the default values for each page search.
@RequestMapping("list")
public String list(@Validated ArticleSearchCriteriaForm form,
BindingResult result,
@PageableDefault( // (1)
page = 0, // (2)
size = 50, // (3)
direction = Direction.DESC, // (4)
sort = { // (5)
"publishedDate",
"articleId"
}
) Pageable pageable,
Model model) {
// ...
return "article/list";
}
Pageable.
(2) To change the default value of location of page, specify the value in page 0
attribute of @PageableDefault annotation. (first page)
Normally it need not be changed.
(4) To change the default value of sort condition, specify the value in Direction.ASC
direction attribute of @PageableDefault annotation. (Ascending order)
(5) Specify the sort fields of sort condition in sort attribute of Empty array
@PageableDefault annotation. (No sort field)
When sorting the records using multiple sort fields, specify the property
name to be sorted in array.
In the above example, sort condition "ORDER BY publishedDate
DESC, articleId DESC" is added to Query.
Note: About sort order that can be specified using @PageableDefault annotation
Sort order that can be specified using @PageableDefault annotation is either ascending or de-
scending; hence when you want to specify different sort order for each field, it is necessary to use
@org.springframework.data.web.SortDefaults annotation. For example, when sorting the
fields using "ORDER BY publishedDate DESC, articleId ASC" sort order.
Tip: Specifying annotation when only the default value of number of records to be fetched is to be
changed
In order to change only the default value of number of records to be fetched, the annotation can also be
See the method below to specify default values using @SortDefaults annotation.
@SortDefaults annotation is used when sorting needs to be done on multiple fields and in order to have
different sort order for each field.
@RequestMapping("list")
public String list(
@Validated ArticleSearchCriteriaForm form,
BindingResult result,
@PageableDefault(size = 50)
@SortDefaults( // (1)
{
@SortDefault( // (2)
sort = "publishedDate", // (3)
direction = Direction.DESC // (4)
),
@SortDefault(
sort = "articleId"
)
}) Pageable pageable,
Model model) {
// ...
return "article/list";
}
Multiple @org.springframework.data.web.SortDefault
annotations can be specified as arrays in @SortDefaults annotation.
@SortDefaults annotation.
Specify as array when specifying multiple annotations.
(3) Specify sort fields in sort or value attribute of @PageableDefault. Empty array
Specify as array when specifying multiple fields. (No sort field)
In the above example, sort condition called "ORDER BY publishedDate DESC, articleId
ASC" is added to query.
Tip: Specifying annotation when only the default value of sort fields is to be specified
In order to specify only the records to be fetched, the annotation can also be specified as
@PageableDefault("articleId"). This operation is same as @PageableDefault(sort
= "articleId") and @PageableDefault(sort = "articleId", direction =
Direction.ASC).
When it is necessary to change the default values of entire application, specify Pageable object wherein default
values are defined in fallbackPageable property of PageableHandlerMethodArgumentResolver
that is defined in spring-mvc.xml.
For description of fallbackPageable and example of settings, refer to “About property values of Pageable-
HandlerMethodArgumentResolver”.
When accessing the database using MyBatis3, extract the necessary information from Pageable object
received from Controller and pass it to the Repository.
Sort conditions and SQL for extracting the corresponding data need to be implemented in SQL mapping.
For details on page search process to be implemented at domain layer, refer to:
When accessing the database using JPA (Spring Data JPA), pass the Pageable object received from Controller
to Repository.
For details on page search process to be implemented at domain layer, refer to:
The method to display pagination link and pagination information (total records, total pages, number of displayed
pages etc.) by displaying the Page object fetched during page search on list screen, is described below.
An example to display the data fetched during page search is shown below.
• Controller
@RequestMapping("list")
public String list(@Validated ArticleSearchCriteriaForm form, BindingResult result,
Pageable pageable, Model model) {
if (!StringUtils.hasLength(form.getWord())) {
return "article/list";
}
return "article/list";
}
(1) Store Page object with the attribute name "page" in Model.
In JSP, Page object can be accessed by specifying attribute name "page".
• JSP
<table class="maintable">
<thead>
<tr>
<th class="no">No</th>
<th class="articleClass">Class</th>
<th class="title">Title</th>
<th class="overview">Overview</th>
<th class="date">Published Date</th>
</tr>
</thead>
<tr>
<td class="no">
${(page.number * page.size) + rowStatus.count}
</td>
<td class="articleClass">
${f:h(article.articleClass.name)}
</td>
<td class="title">
${f:h(article.title)}
</td>
<td class="overview">
${f:h(article.overview)}
</td>
<td class="date">
${f:h(article.publishedDate)}
</td>
</tr>
</c:forEach>
</table>
<div class="paginationPart">
</div>
</c:when>
(2) In the above example, it is checked whether data matching the specified conditions exists. If
there is no such data, the header row is also not displayed.
When it is necessary to display the header row even if there is no matching data, this branching
is no longer required.
(3) Display the list of data fetched using <c:forEach> tag of JSTL.
The fetched data is stored in a list in content property of Page object.
See the example below to display the link for page navigation (pagination link).
• include.jsp
Declare the JSP tag library of common library. The settings below are carried out in a blank project.
(2) EL function of JSP used at the time of using pagination link, is stored.
• JSP
(3) Use <t:pagination> tag. In page attribute, specify Page object stored in Model of
Controller.
• Output HTML
The example below shows the search results obtained upon specifying "?page=0&size=6".
<ul>
<li class="disabled"><a href="javascript:void(0)"><<</a></li>
<li class="disabled"><a href="javascript:void(0)"><</a></li>
<li class="active"><a href="javascript:void(0)">1</a></li>
<li><a href="?page=1&size=6">2</a></li>
<li><a href="?page=2&size=6">3</a></li>
<li><a href="?page=3&size=6">4</a></li>
<li><a href="?page=4&size=6">5</a></li>
<li><a href="?page=5&size=6">6</a></li>
<li><a href="?page=6&size=6">7</a></li>
<li><a href="?page=7&size=6">8</a></li>
<li><a href="?page=8&size=6">9</a></li>
<li><a href="?page=9&size=6">10</a></li>
<li><a href="?page=1&size=6">></a></li>
<li><a href="?page=9&size=6">>></a></li>
</ul>
If style sheet for pagination link is not created, the display will be as follows:
As it is visible below, the pagination link is not established.
The display will be as follows if minimal changes are carried out like adding a definition of style sheet for
pagination link and changing the JSP.
• Screen image
• JSP
<t:pagination page="${page}"
outerElementClass="pagination" /> <%-- (4) --%>
• Style sheet
.pagination li {
display: inline;
}
.pagination li>a {
margin-left: 10px;
}
Even after the pagination link is established, the following two problems still persist.
When Bootstrap v3.0.0 style sheet is applied to resolve the above problems, the display is as follows:
• Screen image
• Style sheet
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
color: #999999;
cursor: not-allowed;
background-color: #ffffff;
border-color: #dddddd;
}
• JSP
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/vendor/bootstrap-3.0.0/css/bootstrap.c
type="text/css" media="screen, projection">
An example to display the information related to pagination (such as total records, total pages and total displayed
pages) is as follows:
• Screen example
• JSP
<div>
<fmt:formatNumber value="${page.totalElements}" /> results <%-- (1) --%>
</div>
<div>
(1) To display total number of data records matching the search conditions, fetch value from
totalElements property of Page object.
(2) To display number of displayed pages, fetch value from number property of Page object and
increment the value by 1.
number property of Page object starts with 0; hence value should be incremented by 1 at the
time of displaying the page number.
(3) To display total pages of data matching the search conditions, fetch the value from
totalPages property of Page object.
Example to display the display data range of the corresponding page is shown below.
• Example of screen
• JSP
<div>
<%-- (4) --%>
<fmt:formatNumber value="${(page.number * page.size) + 1}" /> -
<%-- (5) --%>
<fmt:formatNumber value="${(page.number * page.size) + page.numberOfElements}" />
</div>
(4) To display start location, calculate the value using number property and size property of
Page object.
number property of Page object starts with 0; hence the value needs to be incremented by 1
at the time of displaying data start location.
(5) To display end location, calculate the value using number property, size property and
numberOfElements property of Page object.
numberOfElements needs to be calculated since the last page is likely to be a fraction.
When the numeric value to be displayed needs to be formatted, use tag library ( <fmt:formatNumber>
) provided by JSTL.
The method of carrying forward the search conditions to the page navigation request is shown below.
• JSP
<br>
</form:form>
</div>
<t:pagination page="${page}"
outerElementClass="pagination"
criteriaQuery="${f:query(articleSearchCriteriaForm)}" /> <%-- (2) --%>
(2) When carrying forward the search conditions to page navigation request, specify the encoded
URL query string in criteriaQuery attribute.
When storing the search conditions in form object, conditions can be carried forward easily if
EL function ( f:query(Object) ) provided by common library is used.
In the above example, query string of "?page=page location&size=number of
records to be fetched&word=input value" format is generated.
JavaBean of form object and Map object can be specified as an argument of f:query. In case of JavaBean,
property name is treated as request parameter name and in case of Map object, map key name is treated as
request parameter. URL of the generated query string is encoded in UTF-8.
From the terasoluna-gfw-web 5.0.1.RELEASE, f:query has been supporting a nested structured Jav-
aBean or Map.
Refer to f:query() for detail specification of the f:query (URL encoding specification etc).
Warning: Operations when Query string created using f:query is specified in queryTmpl at-
tribute
It has been found that specifying the query string generated using f:query, in queryTmpl attribute
leads to duplication of URL encoding. Thus, special characters are not carried forward correctly.
This URL encoding duplication can be avoided by using criteriaQuery attribute which can be
used in terasoluna-gfw-web 1.0.1.RELEASE or higher version.
The method to carry forward the sort condition to page navigation request is as follows:
• JSP
<t:pagination page="${page}"
outerElementClass="pagination"
queryTmpl="page={page}&size={size}&sort={sortOrderProperty},{sortOrderDirection}" /> <%-
(1) To carry forward the sort condition to page navigation request, specify queryTmpl and add
sort condition to query string.
For parameter specifications to specify sort condition, refer to “Request parameters for page
search “
In the above example, "?page=0&size=20&sort=sort item, sort order(ASC
or DESC)" is a query string.
Removal of link to navigate to the first page and the last page
Example to remove “Link to navigate to the first page” and “Link to navigate to the last page” is shown below.
• Screen example
• JSP
<t:pagination page="${page}"
outerElementClass="pagination"
firstLinkText=""
lastLinkText="" /> <%-- (1) (2) --%>
(1) Specify "" as firstLinkText attribute of <t:pagination> tag to hide “Link to navigate to
the first page”.
(2) Specify "" as lastLinkText attribute of <t:pagination> tag to hide “Link to navigate to the
last page”.
Example to remove “Link to navigate to the first page” and “Link to navigate to the last page” is shown below.
• Screen example
• JSP
<t:pagination page="${page}"
outerElementClass="pagination"
previousLinkText=""
nextLinkText="" /> <%-- (1) (2) --%>
(1) Specify "" as previousLinkText attribute of <t:pagination> tag to hide “Link to navigate
to the previous page”.
(2) Specify "" as nextLinkText attribute of <t:pagination> tag to hide “Link to navigate to
the next page”.
• Screen example
• Style sheet
.pagination .disabled {
display: none; /* (1) */
}
Example to change maximum number of display links to navigate to the specified page is shown below.
• Screen example
• JSP
<t:pagination page="${page}"
outerElementClass="pagination"
maxDisplayCount="5" /> <%-- (1) --%>
(1) In order to change maximum number of display links to navigate to the specified page, specify
value in maxDisplayCount attribute of <t:pagination> tag.
• Screen example
• JSP
<t:pagination page="${page}"
outerElementClass="pagination"
maxDisplayCount="0" /> <%-- (1) --%>
(1) In order to hide the link to navigate to the specified page, specify "0" as maxDisplayCount
attribute of <t:pagination> tag.
• Screen example
• JSP
<div id="criteriaPart">
<form:form
action="${pageContext.request.contextPath}/article/search"
method="get" modelAttribute="articleSearchCriteriaForm">
<form:input path="word" />
<%-- (1) --%>
<form:select path="sort">
<form:option value="publishedDate,DESC">Newest</form:option>
<form:option value="publishedDate,ASC">Oldest</form:option>
</form:select>
<form:button>Search</form:button>
<br>
</form:form>
</div>
(1) For specifying the sort condition from client, add the corresponding parameters for specifying
the sort condition.
For parameter specifications to specify sort condition, refer to “Request parameters for page
search ” .
In the above example, publishedDate can be selected in ascending order or descending order
from pull-down.
JSP
Disable click event of page link of "disabled" and "active" states by using API of jQuery.
However, when enableLinkOfCurrentPage attribute of <t:pagination> tag is set to
(2)
"true", the click event of page link in "active" state should not be disabled.
(3)
4.4.3 Appendix
fallbackPageable
2.
Specify default values for page location, number of Page location :
records to be fetched and sort condition of the entire 0
application. Number of
When page location, number of records to be fetched and records to be
sort condition are not specified, the values set in fetched : 20
fallbackPageable are used. Sort condition :
null
oneIndexedParameters
3.
Specify start value of page location. false
When false is specified, start value of page location
becomes 0 and when true is specified, it becomes 1.
pageParameterName
4.
Specify request parameter name to specify page location. "page"
sizeParameterName
5.
Specify request parameter name to specify number of "size"
records to be fetched.
prefix
6.
Specify prefix (namespace) of request parameter to ""
specify page location and number of records to be fetched. (No
When there is a conflict between default parameter name namespace)
and the parameter to be used in the application, it is
recommended to specify namespace to avoid this issue.
If prefix is specified, request parameter name to specify
page location will be prefix +
pageParameterName and request parameter name to
specify number of records to be fetched will be prefix
+ sizeParameterName.
566 4 Web Application Development Features
qualifierDelimiter
7.
To search multiple pages in the same request, specify "_"
request parameter name in qualifier +
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Default value is 2000; however it is recommended to change the setting to maximum permissible value
for the application. If maximum permissible value for the application is 100, maxPageSize should also be
set to 100.
It is assumed that the fields given below will normally be changed in each application to be developed. The
example to change the default values of such fields is given below.
• Default values ( fallbackPageable ) of page location and number of records to be fetched in the entire
application
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver">
<!-- (1) -->
<property name="maxPageSize" value="100" />
<!-- (2) -->
<property name="fallbackPageable">
<bean class="org.springframework.data.domain.PageRequest">
<!-- (3) -->
<constructor-arg index="0" value="0" />
<!-- (4) -->
<constructor-arg index="1" value="50" />
</bean>
</property>
<!-- (5) -->
<constructor-arg index="0">
<bean class="org.springframework.data.web.SortHandlerMethodArgumentResolver">
<!-- (6) -->
<property name="fallbackSort">
<bean class="org.springframework.data.domain.Sort">
<!-- (7) -->
<constructor-arg index="0">
<list>
<!-- (8) -->
<bean class="org.springframework.data.domain.Sort.Order">
<!-- (9) -->
<constructor-arg index="0" value="DESC" />
<!-- (10) -->
<constructor-arg index="1" value="lastModifiedDate" /
</bean>
<!-- (8) -->
<bean class="org.springframework.data.domain.Sort.Order">
<constructor-arg index="0" value="ASC" />
<constructor-arg index="1" value="id" />
</bean>
</list>
</constructor-arg>
</bean>
</property>
</bean>
</constructor-arg>
</bean>
</mvc:argument-resolvers>
</mvc:annotation-driven>
(1) In the above example, maximum value of number of records to be fetched is set to 100. When
value specified in number of records to be fetched (size) is 101 or more, search is performed for
100 records only.
(3) Specify default value of page location as the first argument of constructor of PageRequest.
In the above example, 0 is specified, hence the default value is not changed.
(4) Specify default value of number of records to be fetched as the second argument of constructor
of PageRequest.
In the above example, the value will be considered 50 when number of records to be fetched is
not specified in request parameter.
(7) Set the list of Order objects to be used as default value as the first argument of Sort
constructor.
(8) Create an instance of Order and add to the list of Order objects to be used as default value.
In the above example, sort condition of "ORDER BY x.lastModifiedDate DESC,
x.id ASC" is added to query when sort condition is not specified in request parameter.
(9) Specify sort order (ASC/DESC) as the first argument of Order constructor.
sortParameter
2.
Specify request parameter name to specify the sort "sort"
condition.
When there is a conflict between default parameter name
and the parameter to be used in the application, it is
recommended to change the request parameter name to
avoid this issue.
propertyDelimiter
3.
Specify delimiter of sort items and sort order ","
(ASC,DESC).
qualifierDelimiter
4.
To search multiple pages in the same request, specify "_"
request parameter name in ‘‘ qualifier + delimiter +
sortParameter ‘‘ format to distinguish the information
required for page search (such as sort condition).
For this property, set delimiter value of the above
format.
4.5.1 Overview
Problems
If any of the operations given below is performed in a Web application with screens, it leads to same process being
executed multiple times.
(1) Double clicking of ‘Update’ Repeatedly clicking the button to perform update process.
button
(2) Reloading of screen after Using the ‘Refresh’ button of the browser to reload the screen
completing the update process after completion of update process.
(3) Invalid screen transition using Using ‘Back’ button of the browser to go back to the previous
‘Back’ button of the browser page from update process completion screen and clicking the
button again to perform update process.
The following problems occur when the button to perform update process is clicked repeatedly.
The example of “Product Purchase” on a shopping site is given below to explain the problems that are likely to
occur if the necessary measures are not taken.
(2) Buyer accidentally clicks ‘Order’ button again before receiving the response of (1).
(3) Server updates DB based on purchase of the product received through request (1).
(4) Server updates DB based on purchase of the product received through request (2).
(5) Server sends response with a “Purchase Complete” screen for the product received through
request (2).
Warning: In the above case, since buyer accidentally clicks ‘Order’ button again, it results in dupli-
cate purchase of same product. Although the problem can be attributed to erroneous operation by the
buyer, it is desirable to have the application design such that the above problems do not occur.
The following problems occur when the screen is reloaded after completion of update process.
The example of “Product Purchase” on a shopping site is given below to explain the problems that are likely to
occur if the necessary measures are not taken.
(2) Server updates DB based on purchase of the product received through request (1).
(3) Server sends response with a “Purchase complete” screen for the product received through
request (1).
(5) Server updates DB based on the purchase of the product received through request (4).
(6) Server sends response with a “Purchase Complete” screen for the product received through
request (4).
Warning: In the above case, since buyer accidentally executes Reload functionality of the browser, it
results in duplicate purchase of same product. Although the problem can be attributed to erroneous
operation by the buyer, it is desirable to have the application design such that the above problems do
not occur.
The following problems occur if invalid screen transition is performed using ‘Back’ button of the browser.
The example of “Product Purchase” on a shopping site is given below to explain the problems that are likely to
occur if the necessary measures are not taken.
(2) Server updates DB based on the purchase of the product received through request (1).
(3) Server sends response with a “Purchase complete” screen of the product received through
request (1).
(4) Buyer uses ‘Back’ button of the browser to go back to “Product Purchase” screen.
(5) Buyer again clicks ‘Order’ button on “Product Purchase” screen which is re-displayed by
clicking ‘Back’ button of the browser.
(6) Server updates DB based on the purchase of the product received through request (5).
(7) Server sends response with a “Purchase Complete” screen for the product received through
request (5).
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: In the above case, since buyer does not perform any erroneous operation, the problem is not
attributed to the buyer.
However, if update process gets executed even after performing invalid screen operations, the following problems
occur.
Warning: As described above, update process getting executed even after performing invalid screen
operations increases the risk of direct updates by a malicious attacker bypassing a valid route.
Sr. No. Description
(1) Attacker executes request for processing a direct product purchase without going
through a valid screen transition.
(2) Server cannot detect that the request is getting executed through an invalid route;
hence it updates DB based on the purchase of the product received through that
request.
Execution of purchase process through an invalid request increases the load on each server resulting in
inability to purchase products through a valid route. As a result, the problem causes a ripple effect for
the users who purchase the products through valid routes. Hence, it is desirable to have the application
design such that the above problems do not occur.
Solutions
The following measures should be taken to resolve the problems described above.
In view of malicious operations such as tampering with requests, (3) “Applying transaction token check” is
mandatory.
(1) Preventing double When a button to perform update process is clicked, the button control
clicking of a button using JavaScript prevents the submission of a request if the button is
using JavaScript clicked again.
(2) Applying PRG A redirect command is returned as a response to the request for
(Post-Redirect-Get) performing update process (request by POST method) and then a screen
pattern for transition is returned as a response of GET method which is
automatically requested from a browser.
When a PRG pattern is used, the request generated while reloading the
page after the screen is displayed is a GET method; hence re-execution of
update process can be prevented.
(3) Applying transaction Issue a token value for each screen transition and compare the token value
token check sent from browser with the token value stored on the Server to make sure
that invalid screen operations do not occur in the transaction.
Implementation of transaction token check can prevent re-execution of
update process after the page is reloaded using ‘Back’ button of the
browser.
Deleting the token value stored on the Server after performing the token
check can prevent double submission as a Server side process.
Note: When only transaction token check is performed, even a simple operational mistake can lead to
transaction token error which in turn results in a low-usability application for the user.
To ensure usability as well as to prevent the problems that occur due to double submission, measures such as
“Preventing double clicking of a button using JavaScript” and “Applying PRG (Post-Redirect-Get) pattern”
are necessary.
** Although this guideline recommends that you implement all the measures, the decision should be taken
depending on application requirements.**
Warning: In Ajax and Web services, since it is difficult to transfer transaction tokens which change
for each request, transaction token check need not be used. In Ajax, double submit protection should
be performed using only one of the above measures i.e. “Preventing double clicking of a button using
JavaScript”.
Todo
TBD
There is further scope for reviewing the check methods in Ajax and Web services.
Prevent double clicking of buttons like button to perform update process or button which is used to perform
time-consuming search process.
When a button is clicked, use JavaScript to disable that button or link.
Typical examples of control used for disabling a button or link are given below
2. By maintaining a flag for tracking process status and displaying notification “Process in progress” when
the button or link is clicked in the middle of the process.
Warning: If all the buttons and links on the screen are disabled, the screen operations can no longer be
performed if there is no response from the Server. Therefore, it is recommended not to disable buttons
or links that execute events such as “Return to previous screen” or “Go to top screen” etc.
A redirect command is returned as a response to the request for performing update process (request by POST
method) and then a screen for transition is returned as a response of GET method which is automatically
requested from a browser.
When a PRG pattern is used, the request generated while reloading the page after the screen is displayed is a
GET method; hence re-execution of update process can be prevented.
(2) Server updates DB based on the purchase of the product received through request (1).
(3) Server sends a redirect response for the URL to display the “Purchase Complete” screen
for the product.
(4) Browser submits request for the URL to display the “Purchase Complete” screen for the
product.
The request is submitted using GET method.
(5) Server sends response with a “Purchase Complete” screen for the product.
Note: It is recommended to use PRG pattern for the processes associated with update process and imple-
ment a control so that a request of GET method is sent when ‘Refresh’ button of the browser is clicked.
Warning: In the PRG pattern, the re-execution of update process cannot be prevented by clicking
‘Back’ button of the browser on Completion screen. A transaction token check must be performed
to prevent re-execution of update process after an invalid screen transition using ‘Back’ button of the
browser.
• When a request is received from Client, the Server stores a value (hereafter referred to as transaction token)
for uniquely identifying a transaction on the Server.
• Server passes the transaction token to the Client. In case of a Web application with screens, it passes the
transaction token to the Client using hidden tag of form.
• When submitting the next request, Client sends the transaction token that was passed from the Server.
Server compares the transaction token received from the Client and the transaction token stored on the
Server.
When the transaction token sent in the request does not match with the transaction token stored on the Server, it is
treated as invalid request and an error is returned.
Warning: Misuse of transaction token check leads to poor usability of the application; hence the scope
of its usage should be defined by considering the following points.
• It is not necessary to include reference-type requests that do not involve data update and
requests that perform only screen transitions in the scope of transaction token check.
If the scope of transactions is extended unnecessarily, transaction token errors are more likely to
occur which in turn reduces the usability of the application.
• Transaction token check is not mandatory for the processes wherein there is no problem even if
the data gets updated multiple times from business perspective (user information update etc.).
• Transaction token check is mandatory for the processes such as deposit process or product
purchase process etc. wherein there is a risk of duplicate execution.
The process flow when expected operations are performed and process flow when unexpected operations are
performed using transaction token check are shown below.
(2) Server creates the transaction token (token001) and stores it on the Server.
(3) Server passes the created transaction token (token001) to the Client.
(4) Client sends the request along with the transaction token (token001).
(5) Server checks whether the transaction token (token001) stored on the Server and the transaction
token (token001) submitted by the Client are same.
Since the values are same, the request is considered as valid.
(6) Server generates transaction token (token002) to be used in the next request and updates the
value stored on the Server.
At this point, the transaction token (token001) is discarded.
(7) Server passes the updated transaction token (token002) to the Client.
(9) Request is sent from Client side along with the transaction token (token001) of the screen
which is displayed after clicking ‘Back’ button.
(10) Server checks whether the transaction token (token002) stored on the Server and the transaction
token (token001) submitted by the Client are same.
Since the values are different, the request is considered as invalid and a transaction token
error is thrown.
(11) Server sends response with an error screen to notify that a transaction token error has occurred.
• Invalid screen transition in case of a business process that requires fixed screen transition
• Data update due to invalid requests that do not involve valid screen transitions
Invalid screen transition in case of a business process that requires fixed screen transition, can be prevented by the
flow shown below.
(2) Server updates DB based on the purchase of the product received through request (1).
(3) Server sends response with a “Purchase Complete” screen for the product received through
request (1).
Data updated by an invalid request which does not involve a valid screen transition can be prevented by the flow
shown below.
(1) Attacker sends a request to purchase the product directly without performing a valid screen
transition.
Since the request for generating a transaction token is not executed, a transaction token
error occurs.
(2) Server sends response with an error screen to notify that a transaction token error has occurred.
Duplicate execution of update process at the time of double submission can be prevented by the flow shown below.
(2) Buyer accidentally clicks ‘Order’ button again before the response of (1) is returned.
Since the transaction token sent by the Client is a value which has already been discarded,
a transaction token error occurs when process of (1) is executed.
(3) Server sends response with an error screen to notify that a transaction token error has
occurred for the request (2).
(4) Server updates DB based on the purchase of the product received through request (1).
(5) Server attempts to respond with a “Purchase Complete” screen for the product received through
request (1); however since the stream for responding to the request of (1) is closed due to the
transmission of the request of (2), it fails to send response with a “Purchase Complete” screen.
Warning: This can prevent duplicate execution of update process at the time of double submission;
however this does not resolve the problem of server not being able to respond with a screen to notify
the completion of process. Therefore, it is also recommended to deal with this problem by preventing
double clicking of a button using JavaScript.
In the transaction token check functionality provided by the common library, it is possible to set a NameSpace in
the container for storing transaction token. This enables parallel execution of update process using a tab browser
or multiple windows.
(1) The request is sent from Window 1, then the transaction token (token001) received as a
response is stored in the browser.
The transaction token stored on the server is considered token001.
(2) The request is sent from Window 2, then the transaction token (token002) received as a
response is stored in the browser.
The transaction token stored on the server is considered token002. The transaction token
(token001) generated by the process (1) is discarded at this point.
(3) The request is sent from Window 1 along with the transaction token (token001) stored in the
browser.
Since the transaction token stored on the server (token002) and the transaction token sent by the
request (token001) do not match, the request is considered as invalid resulting in a transaction
token error.
Warning: The update process cannot be executed concurrently in absence of NameSpace; hence
the application becomes a low-usability application.
** When a NameSpace is specified, the transaction token in the NameSpace allocated to the transaction is
handled independently. Hence, it does not affect the transactions of another NameSpace.**
Here, even though the browser is explained using different Windows, the tab browser works in the same way. For
generated keys and usage method, refer to Using transaction token check.
Todo
TBD
The image numbers and comment number of source code are linked.
However, since (1)-(4) is not directly related to the PRG pattern, the explanation is omitted.
• Controller
@Controller
@RequestMapping("prgExample")
public class PostRedirectGetExampleController {
@Inject
UserService userService;
@ModelAttribute
public PostRedirectGetForm setUpForm() {
PostRedirectGetForm form = new PostRedirectGetForm();
return form;
}
@RequestMapping(value = "create",
method = RequestMethod.GET,
params = "form") // (1)
public String createForm(
PostRedirectGetForm postRedirectGetForm,
BindingResult bindingResult) {
return "prg/createForm"; // (2)
}
@RequestMapping(value = "create",
method = RequestMethod.POST,
params = "confirm") // (3)
public String createConfirm(
@Validated PostRedirectGetForm postRedirectGetForm,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "prg/createForm";
}
return "prg/createConfirm"; // (4)
}
@RequestMapping(value = "create",
method = RequestMethod.POST) // (5)
public String create(
@Validated PostRedirectGetForm postRedirectGetForm,
BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return "prg/createForm";
}
// omitted
@RequestMapping(value = "create",
method = RequestMethod.GET,
params = "complete") // (7)
(5) A handler method to perform a process when ‘Register’ button (Create User button) on
Confirmation screen is clicked.
The request is received by POST method.
(8) View (JSP) is called to display the Completion screen and responds with Completion screen.
Since the extension of JSP is assigned by ViewResolver defined in spring-mvc.xml, it
is omitted from the return value of the handler method.
Note:
• At the time of redirecting, assign “redirect:” as the prefix of transition information to be returned by
the handler method as the return value.
• When the data is to be delivered to the process of redirect destination, call addFlashAttribute method
of RedirectAttributes and add the data to be delivered.
• createForm.jsp
<h1>Create User</h1>
<div id="prgForm">
<form:form
action="${pageContext.request.contextPath}/prgExample/create"
method="post" modelAttribute="postRedirectGetForm">
<form:label path="firstName">FirstName</form:label>
<form:input path="firstName" /><br>
<form:label path="lastName">LastName:</form:label>
<form:input path="lastName" /><br>
<form:button name="confirm">Confirm Create User</form:button>
</form:form>
</div>
• createConfirm.jsp
(6) When the button to perform update process is clicked, a request is sent by POST method.
• createComplete.jsp
(7) When the data delivered from update process is to be referred at the redirect destination,
specify the attribute name of the data added by the addFlashAttribute method of
RedirectAttributes.
In the above example, "output" is the attribute name to refer to the delivered data.
The transaction token check can be performed declaratively by assigning @TransactionTokenCheck anno-
tation for the Controller class and the handler methods of the Controller class.
The attributes that can be specified in @TransactionTokenCheck annotation are explained below.
namespace None
2.
Any string.Used as NameSpace. namespace =
type IN
3.
BEGIN type =
A transaction token is created and a new TransactionTokenType.
transaction is started.
type =
IN TransactionTokenType.
CHECK
Transaction token is validated.
Even when the requested token value and the
token value stored on the server match,the
4.5. Double Submit Protectiontoken value of transaction token is not updated. 597
For used cases, refer “When a process which
does not update screen for file downloading
process is included in the use case”.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: It is recommended that the value to be set in value attribute or namespace attribute should be same
as the config value of “value” attribute for @RequestMapping annotation.
Note: In “type” attribute, NONE and END can be specified; however, the description is omitted as
normally they are not used.
Format for the transaction token used in the transaction token check of common library is as follows:
TokenKey
• TokenKey is an element for identifying the transactions stored in the
(2) Namespace.
4.5. Double Submit Protection • TokenKey is generated upon execution of a 599wherein
method
TransactionTokenType.BEGIN is declared in the “type” attribute
of @TransactionTokenCheck annotation.
• A maximum limit exists for the number of multiple TokenKeys which can
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: When the “value” attribute is specified only in “method” annotation and if the same value
is specified in another Controller, it should be noted that it will be handled as a request for carrying out
a series of screen transitions. “value” attribute should be specified by this method only when the screen
transitions across Controllers are to be treated as the same transaction.
Basically, it is recommended not to use the method wherein “value” attribute is specified only in
“method” annotation.
Note: The method for specifying NameSpace is classified according to creation granularity of the Con-
troller,
• when “value” attributes of both “class” annotation and “method” annotation are specified
The lifecycle (Generate, Update, Discard) control of transaction token is performed in the following scenarios.
(1) Token Generation A new token is generated and transaction is started when the processing of
the method wherein TransactionTokenType.BEGIN is specified in
“type” attribute of @TransactionTokenCheck annotation, is
terminated.
(2) Token Update The token (TokenValue) is updated and transaction is continued when the
processing of the method wherein TransactionTokenType.IN is
specified in “type” attribute of @TransactionTokenCheck
annotation, is terminated.
(3) Token Discard The tokens are discarded in any of the following scenarios and the
transaction is terminated.
[1]
When the method wherein ‘‘TransactionTokenType.BEGIN‘‘ is specified
in “type” attribute of @TransactionTokenCheck annotation, is
called, the transaction token specified in the request parameter is
discarded and unnecessary transaction is terminated.
[2]
If a new transaction starts when number of transaction tokens (TokenKey)
that can be stored in NameSpace has reached the maximum limit, the
transaction token with the oldest date and time of execution is discarded
and the transaction is forcibly terminated.
[3]
When exceptions such as system error occur, the transaction token
specified in the request parameter is discarded and the transaction is
terminated.
(4) Token inherited Token on the server is inherited within the termination of the process for
the method which specifies TransactionTokenType.CHECK in type
attribute of @TransactionTokenCheck annotation and the
transaction is continued.
Note: A maximum limit is provided for the number of transaction tokens (TokenKey) which can be stored
in a NameSpace. When the maximum limit is reached while generating a new transaction token, a new
transaction is managed as a valid transaction, by discarding the transaction token which has the TokenKey
with the oldest date and time of execution (Least Recently Used (LRU)).
The maximum limit of transaction tokens that can be stored for each NameSpace is 10 by default. To
change the maximum limit, refer to How to change the maximum limit of transaction tokens .
The behavior when the maximum limit is reached while generating a new transaction token is explained below.
The pre-requisites are as given below.
• A default value (10) is specified as the maximum limit for the number of transaction tokens that can be
stored in the NameSpace.
• Transaction tokens of the same NameSpace have reached the maximum limit.
(1) A request to start a new transaction is received when the transaction tokens of the same
NameSpace have reached the maximum limit.
(3) The generated transaction token is added to the location where tokens are stored.
** At this point, the transaction tokens that exceed the maximum limit are present in the
NameSpace.**
(4) The transaction tokens exceeding the maximum limit are deleted from the NameSpace.
The transaction tokens are deleted in a sequence starting with the transaction token with
the oldest date and time of execution.
The settings for using the transaction token check provided by the common library are shown below.
• spring-mvc.xml
<mvc:interceptors>
<mvc:interceptor> <!-- (1) -->
<mvc:mapping path="/**" /> <!-- (2) -->
<mvc:exclude-mapping path="/resources/**" /> <!-- (2) -->
<mvc:exclude-mapping path="/**/*.html" /> <!-- (2) -->
<!-- (3) -->
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<!-- (4) -->
<bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestData
<!-- omitted -->
</util:list>
</constructor-arg>
</bean>
Therefore, in order to handle transaction token errors, it is necessary to add the handling definition of
InvalidTransactionTokenException to the following settings.
• Common Settings
In order to perform transaction token check, it is necessary to define the method to start the transaction and the
method to carry out the checks in Controller.
The explanation below is about the implementation of handler method required in a single usecase using a
controller.
• Controller
@Controller
@RequestMapping("transactionTokenCheckExample")
@TransactionTokenCheck("transactionTokenCheckExample") // (1)
public class TransactionTokenCheckExampleController {
(3) The transaction token is checked and transaction token value is updated.
If the type attribute is omitted, the behavior remains the same as when
@TransactionTokenCheck(type = TransactionTokenType.IN) is specified.
(4) Since it is not necessary to perform transaction token check in the request for displaying a
screen which notifies the completion of the usecase, @TransactionTokenCheck
annotation is not specified.
Note:
When transaction token is to be checked, View (JSP) should be implemented in such a way that the issued
transaction token is submitted as a request parameter.
A method wherein a transaction is automatically embedded in hidden elements by using <form:form> tag is
recommended as a method to submit it as a request parameter after carrying out Settings for using a transaction
token check.
• firstView.jsp
<h1>First</h1>
<form:form method="post" action="transactionTokenCheckExample">
<input type="submit" name="second" value="second" />
</form:form>
• secondView.jsp
<h1>Second</h1>
<form:form method="post" action="transactionTokenCheckExample"><!-- (1) -->
<input type="submit" name="third" value="third" />
</form:form>
• thirdView.jsp
<h1>Third</h1>
<form:form method="post" action="transactionTokenCheckExample"><!-- (1) -->
<input type="submit" name="fourth" value="fourth" />
</form:form>
• fourthView.jsp
<h1>Fourth</h1>
<form:form method="post" action="transactionTokenCheckExample"><!-- (1) -->
<input type="submit" name="fifth" value="fifth" />
</form:form>
<h1>Fourth</h1>
<form method="post" action="transactionTokenCheckExample">
<t:transaction /><!-- (2) -->
<!-- (3) -->
<input type="hidden" name="${f:h(_csrf.parameterName)}"
value="${f:h(_csrf.token)}"/>
<input type="submit" name="fifth" value="fifth" />
</form>
• fifthView.jsp
<h1>Fifth</h1>
<form:form method="get" action="transactionTokenCheckExample">
<input type="submit" name="first" value="first" />
</form:form>
(1) When <form:form> tag is used in JSP and if BEGIN or IN is specified in “type” attribute of
@TransactionTokenCheck annotation, the Value of name="_TRANSACTION_TOKEN"
is automatically embedded as a hidden tag.
(2) When <form> tag of HTML is used, a hidden tag same as (1) is embedded using
<t:transaction />.
(3) When <form> tag of HTML is used, csrf token necessary for CSRF token check provided by
Spring Security needs to be embedded as a hidden item.
For csrf token necessary for CSRF token check, refer to Coordination by using Spring MVC.
Note: If <form:form> tag is used, the parameters necessary for CSRF token check are also automat-
ically embedded. Refer to Coordination while using HTML form for the parameters necessary for CSRF
token check.
Note: <t:transaction /> is a JSP tag library provided by the common library. For the “t:” used in
(2), refer to Creating common JSP for include.
• For NameSpace, the value specified in “value” attribute of the class annotation is set.
In the above example, "transactionTokenCheckExample" (underlined in orange) is the
NameSpace.
• For TokenKey, the value that was issued at the start of the transaction is circulated and set.
In the above example, "c0123252d531d7baf730cd49fe0422ef" (underlined in blue) is the
TokenKey.
The implementation example of the transaction token check while carrying out processing of multiple usecases in
one controller is given below.
In the example given below, (2), (3), (4) are handled as screen transitions of different usecases.
• Controller
@Controller
@RequestMapping("transactionTokenChecFlowkExample")
@TransactionTokenCheck("transactionTokenChecFlowkExample") // (1)
public class TransactionTokenCheckFlowExampleController {
@RequestMapping(value = "flowOne",
params = "first",
method = RequestMethod.GET)
public String flowOneFirst() {
return "transactionTokenChecFlowkExample/flowOneFirstView";
@RequestMapping(value = "flowOne",
params = "second",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowOne",
type = TransactionTokenType.BEGIN) // (2)
public String flowOneSecond() {
return "transactionTokenChecFlowkExample/flowOneSecondView";
}
@RequestMapping(value = "flowOne",
params = "third",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowOne",
type = TransactionTokenType.IN) // (2)
public String flowOneThird() {
return "transactionTokenChecFlowkExample/flowOneThirdView";
}
@RequestMapping(value = "flowTwo",
params = "first",
method = RequestMethod.GET)
public String flowTwoFirst() {
return "transactionTokenChecFlowkExample/flowTwoFirstView";
}
@RequestMapping(value = "flowTwo",
params = "second",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowTwo",
type = TransactionTokenType.BEGIN) // (3)
public String flowTwoSecond() {
return "transactionTokenChecFlowkExample/flowTwoSecondView";
}
@RequestMapping(value = "flowTwo",
params = "third",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowTwo",
type = TransactionTokenType.IN) // (3)
public String flowTwoThird() {
return "transactionTokenChecFlowkExample/flowTwoThirdView";
}
@RequestMapping(value = "flowThree",
params = "first",
method = RequestMethod.GET)
public String flowThreeFirst() {
return "transactionTokenChecFlowkExample/flowThreeFirstView";
}
@RequestMapping(value = "flowThree",
params = "second",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowThree",
type = TransactionTokenType.BEGIN) // (4)
public String flowThreeSecond() {
return "transactionTokenChecFlowkExample/flowThreeSecondView";
}
@RequestMapping(value = "flowThree",
params = "third",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "flowThree",
type = TransactionTokenType.IN) // (4)
public String flowThreeThird() {
return "transactionTokenChecFlowkExample/flowThreeThirdView";
}
(2) The transaction token is checked for processing of usecase with name "flowOne".
In the above example, the value same as the “value” attribute of @RequestMapping which is
a recommended pattern of this guideline, is specified.
(3) The transaction token is checked for processing of usecase with name "flowTwo".
In the above example, the value same as the “value” attribute of @RequestMapping which is
a recommended pattern of this guideline, is specified.
(4) The transaction token is checked for processing of usecase with name "flowThree".
In the above example, the value same as the “value” attribute of @RequestMapping which is
a recommended pattern of this guideline, is specified.
Note: Allocating a NameSpace for each usecase enables transaction token check for each usecase.
When a process which does not update screen for file downloading process is included in the use case
For the use cases which include process wherein screen is not returned to the client during file downloading
process, when the TransactionTokenType is not configured appropriately, care must be taken since the transaction
token error is likely to occur even during normal operations.
An example wherein the transaction token error occurs during a normal operation due to specification of inappro-
priate TransactionTokenType is shown below.
(3) Server delivers the token thus created (token001) to the client.
(4) Client sends a file downloading request including the token (token001).
(5) Server checks whether the token retained on the server (token001) and the token sent by the
client (token001) are identical.
Since the value is same, the request is determined as a legitimate request.
(9) Server checks whether the token retained on the server (token001) and the token sent by the
client (token002) are identical.
Since the value is not the same, the request is determined as an illegitimate request.
As shown above, when the transaction token is applied for the transition from screen wherein file downloading
process is performed (screen2) to next screen (screen3), token mismatch occurs during normal operation if IN
is used in TransactionTokenType of file downloading process. For these cases, CHECK is used in the
TransactionTokenType.
(2) Server checks whether the token retained on the server (token001) and the token sent by client
(token001) are identical.
Since the value is same, the request is determined as a legitimate request.
(3) Since CHECKis configured in TransactionTokenType, the token retained on the server is
not updated.
(6) Server checks whether the token retained on the server (token001) and the token sent by client
(token001) are identical.
Since the value is same, the request is determined as a legitimate request.
(7) Server generates a token to be used in the next request (token002) and updates the value
retained on the server.
The token (token001) is discarded at this point.
See the example below wherein transaction token check is applied for the usecase which carries out a simple
screen transition such as “Input Screen-> Confirmation Screen-> Completion Screen”.
• Controller
@Controller
@RequestMapping("user")
@TransactionTokenCheck("user") // (1)
public class UserController {
// omitted
@RequestMapping(value = "create",
params = "confirm",
method = RequestMethod.POST)
@TransactionTokenCheck(value = "create",
type = TransactionTokenType.BEGIN) // (3)
public String createConfirm(@Validated
UserCreateForm form, BindingResult result) {
// omitted
return "user/createConfirm";
}
// omitted
return "redirect:/user/create?complete";
}
// omitted
(3) A handler method to perform input validation and display the Confirmation screen.
A transaction is started at this point since a button to perform update process is placed on the
Confirmation screen.
A View (JSP) is specified for the transition destination.
Warning: It is necessary to specify the View (JSP) for the transition destination of handler method
wherein @TransactionTokenCheck annotation is defined. If other than View (JSP) of the redirect
destination is specified as transition destination, the value of TransactionToken changes in the next
process always resulting in the TransactionToken error.
When a form object is stored in a session using @SessionAttributes annotation, and if multiple screen
transitions of the same processing are performed in parallel, screen operations may interfere with each other and
the values displayed on the screen and the values stored in the session may no longer match.
Transaction token check function can be used to prevent requests from non-conforming screens as the invalid
requests.
The maximum limit of transaction tokens that can be stored for each NameSpace is set to 1.
• spring-mvc.xml
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- omitted -->
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor">
<constructor-arg value="1"/> <!-- (1) -->
</bean>
</mvc:interceptor>
(1) The number of transaction tokens stored for each NameSpace is set to “1”.
Note: When form objects etc. are stored in session using @SessionAttributes annotation, the
requests from the screen displaying old data can be prevented as invalid requests by setting number of
transaction token stored for each NameSpace to “1”.
The maximum limit of transaction tokens that can be stored on 1 NameSpace can be changed by performing
settings given below.
• spring-mvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
<constructor-arg value="5"/> <!-- (1) -->
</mvc:interceptor>
</mvc:interceptors>
(1) The maximum limit of transaction tokens that can be stored in a NameSpace is specified as a
value of constructor of TransactionTokenInterceptor.
The default value (value that is set when the default constructor is used) is 10.
In the above example, the default value (10) is changed to 5.
4.5.4 Appendix
If the cache of web browser is disabled due to Cache-Control in HTTP response header, the message will
display which tells the cache has been expired before transaction token error in case of the illegal operation flow
in “Transaction Token Check“
Concretely, the following screen will display when the browser back button is clicked (8). This is an example of
Internet Explorer 11.
In blank projects after 5.0.0.RELEASE, it is configured so that the cache is disabled by Spring Security.
If showing the transaction error screen is preferred instead of the screen above, excluding
<sec:cache-control /> is required. However, <sec:cache-control /> should be configured
from the point of view of security.
Global Tokens
Note: When only a single screen transition is to be allowed as an overall application, it can be implemented
by setting the maximum limit of transaction tokens that can be stored for each NameSpace to 1 and using
the global token.
The settings and implementation example when only a single screen transition is to be allowed as an overall
application are shown below.
Changing the maximum limit of transaction tokens that can be stored for each NameSpace
The maximum limit of transaction tokens that can be stored for each NameSpace is set to 1.
• spring-mvc.xml
<mvc:interceptor>
<mvc:mapping path="/**" />
<!-- omitted -->
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor">
<constructor-arg value="1"/> <!-- (1) -->
</bean>
</mvc:interceptor>
(1) The number of tokens stored for each NameSpace is set to “1”.
Implementation of Controller
The value is not specified in “value” attribute of @TransactionTokenCheck annotation, in order to make it
the NameSpace for global tokens.
• Controller
@Controller
@RequestMapping("globalTokenCheckExample")
public class GlobalTokenCheckExampleController { // (1)
JSP used is same as the one created in How to use transaction token check in View (JSP) .
Other details are same except change in action from "transactionTokenCheckExample"to
"globalTokenCheckExample".
• For TokenKey, the value that was issued while starting the transaction is circulated and set.
In the above example, "9d937be4adc2f5dd2032292d153f1133" (underlined in blue) is the
TokenKey.
The behavior when the maximum limit of transaction tokens for each NameSpace is set to 1 and global token is
used, is explained below.
Note: The transaction token existing on the server is automatically deleted when a new global token is
generated.
Quick Reference
The table below illustrates an example of a business application for managing Account and Customer. It shows
the required settings for transaction tokens and business limitations.
The usecases assumed in this business application are Create, Update, Delete relating to Account and Customer.
By using the table below as reference, the maximum limit of tokens and NameSpace settings should be
performed as per the system requirements.
4.6.1 Overview
A message consists of fixed text displayed on screens or reports, or dynamic text displayed depending on screen
operations performed by user.
It is also recommended to define a error message in as much details as possible.
Warning: In following cases, there is a risk of inability to identify error cause during the production
phase or during the testing just before entering into production phase (however, such risks may not
surface during the development phase).
• When only one error message is defined
• When only two types of error messages (“Important” and “Warning”) are defined
Thus, if messages are changed when the number of developers in the team is less, the cost to modify
these messages would increase as the development progresses. It is, therefore, recommended to define
the messages in advance at a detailed level.
Types of messages
Message that display according to the result of the user’s screen operation should be classified into following 3
types depending on the message contents.
When defining any message, it important to note its type.
Message patterns, message display contents and the message type are shown below.
• User name
Label Screen field name - • Password
Report field name
Comment
Guidance
• Registered.
(C) Result message Successful completion info • Deleted.
Message ID
• To support internationalization
From maintenance perspective, it is strongly recommended that you define the message IDs by creating and
standardizing the rules.
See the example below for Message ID rules for each message pattern.
Refer to these rules when message ID rules are not defined in a development project.
Title
• Format
. .
title nnn* nnn*
• Description
• Example
# In case of "/WEB-INF/views/admin/top.jsp"
title.admin.top=Admin Top
# In case of "/WEB-INF/views/staff/createForm.jsp"
title.staff.createForm=Staff Register Input
Tip: This example is valid when using Tiles. For details, refer to Screen Layout using Tiles. When
not using Tiles, follow the Labels rules explained later.
Labels
The method of defining message ID to be used in screen label and fixed text of reports is described below.
• Format
. xx . .
label nnn* nnn*
• Description
Business Variable
process length:
name Optional
Note: When including the field name into validation error message, define messages as follows.
– filed name
• Example
Note: In case of multiple projects, define a project code to avoid duplication of message ID. Even if
there is a single project, it is recommended to define a project code for future enhancements.
Result messages
Messages commonly used in business processes To avoid duplication of messages, the messages which
are common in multiple business processes are explained below.
• Format
Message Delimiter Project code Delimiter Common Delimiter Error level Sr. No
type message
code
x . xx . .
fw 9 999
• Description
Sr. No. 10th -12th digit (3 Use as per serial number (000-999) Even if
digits) the
message
is deleted,
serial
number
field
should be
blank and
it should
not be
deleted.
• Example
Messages used individually in each business process The messages used individually in each business
process are explained below.
• Format
Message Delimiter Project code Delimiter Business Delimiter Error level Sr. No
type process
message
code
x . xx . xx .
9 999
• Description
Business process 6th -7th digit (2 2 characters defined for each business
message code digits) process such as Business ID
Sr. No. 10th -12th digit (3 Use as per serial number (000-999) Even if
digits) the
message
is deleted,
serial
number
field
should be
blank and
it should
not be
deleted.
• Example
For the messages to be displayed in case of input validation error, refer to Definition of error messages.
Note: Basic policies related to output location of input validation error are as follows:
• Single field input validation error messages should be displayed next to the target field so that it can
be identified easily.
• Correlation input validation error messages should be displayed collectively on the top of the page .
• When it is difficult to display the single field validation message next to the target field, it should be
displayed on the top of the page.
In that case, field name should be included in the message.
• applicationContext.xml
(2) Define the base name of message property to be used. Specify it with relative class path.
In this example, read “src/main/resources/i18n/application-messages.properties”.
• application-messages.properties
label.aa.bb.year=Year
label.aa.bb.month=Month
label.aa.bb.day=Day
Note: Earlier, it was necessary to convert the characters (such as Japanese characters etc.) that
cannot be expressed into “ISO-8859-1” with the help of native2ascii command. However, from
JDK version 6 onwards, it has become possible to specify the character encoding; hence character
conversion is no longer needed. By setting the character encoding to UTF-8, Japanese characters etc.
can be used directly in properties file.
– application-messages.properties
label.aa.bb.year= Year
label.aa.bb.month= Month
label.aa.bb.day= Day
In such a case, it is necessary to specify the character encoding that can also be read in
ResourceBundleMessageSource .
– applicationContext.xml
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>i18n/application-messages</value>
</list>
</property>
<property name="defaultEncoding" value="UTF-8" />
</bean>
ISO-8859-1 is used by default; hence when describing the Japanese characters directly in properties
file, make sure that the character encoding is set as value of defaultEncoding property.
• JSP
Messages set above can be displayed using <spring:message> tag in JSP. For using it, settings
mentioned in Creating common JSP for include must be done.
<form:form modelAttribute="sampleForm">
<form:label path="year">
<spring:message code="label.aa.bb.year" />
</form:label>: <form:input path="year" />
<br>
<form:label path="month">
<spring:message code="label.aa.bb.month" />
</form:label>: <form:input path="month" />
<br>
<form:label path="day">
<spring:message code="label.aa.bb.day" />
</form:label>: <form:input path="day" />
</form:form>
src/main/resources/i18n
├ application-messages.properties (English message)
├ application-messages_fr.properties (French message)
├ ...
└ application-messages_ja.properties (Japanese message)
properties file should be created for each language as shown above. For details, refer to International-
ization .
<t:messagesPanel> tag is also provided as JSP tag library for displaying this result message in JSP.
The way of creating ResultMessages in Controller, passing them to screen and displaying the result messages
using <t:messagesPanel> tag in JSP, is displayed below.
• Controller class
The methods of creating ResultMessages object and passing the messages to screen are given
below. An example of Messages used individually in each business process should be defined in
application-messages.properties.
package com.example.sample.app.message;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.terasoluna.gfw.common.message.ResultMessages;
@Controller
@RequestMapping("message")
public class MessageController {
@RequestMapping(method = RequestMethod.GET)
public String hello(Model model) {
ResultMessages messages = ResultMessages.error().add("e.ex.an.9001"); // (1)
model.addAttribute(messages); // (2)
return "message/index";
}
}
ResultMessages.error().add(ResultMessage.fromCode("e.ex.an.9001"));
Since it is possible to skip the creation of ResultMessage object if message ID is
specified, it is recommended to skip the same.
• JSP
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Result Message Example</title>
</head>
<body>
<h1>Result Message</h1>
HTML output by <t:messagesPanel> is shown below. (The format makes the explanation eas-
ier).
(1) “alert-error”class is assigned in accordance with the message type. “error error-[Message
type]” is assigned to <div> tag class by default.
<t:messagesPanel> outputs only HTML with class; hence it is necessary to customize the look
and feel using CSS as per the output class (explained later).
For inserting a value in message placeholder, set second or subsequent arguments of add method as follows:
In such a case, the HTML shown below is output using <t:messagesPanel /> tag.
e.ex.an.8001=Cannot upload, Because the file size must be less than {0,number,#}MB.
(1) The output class name has changed to “alert alert-info” in accordance with the message type.
CSS should be defined according to the message type. Example of applying CSS is given below.
.alert {
margin-bottom: 15px;
padding: 10px;
border: 1px solid;
border-radius: 4px;
text-shadow: 0 1px 0 #ffffff;
}
.alert-info {
background: #ebf7fd;
color: #2d7091;
border-color: rgba(45, 112, 145, 0.3);
}
.alert-warning {
background: #fffceb;
color: #e28327;
border-color: rgba(226, 131, 39, 0.3);
}
.alert-error {
background: #fff1f0;
color: #d85030;
border-color: rgba(216, 80, 48, 0.3);
}
Note: “success” and “danger” are provided to have diversity in style. In this guideline, success is
synonymous with info and error is synonymous with danger.
Tip: Alerts component of Bootstrap 3.0.0 which is a CSS framework can be used with default settings
of <t:messagePanel />.
Warning: In this example, message keys are hardcoded. However, in order to improve maintain-
ability, it is recommended to define message keys in constant class.
Refer to Auto-generation tool of message key constant class.
model.addAttribute("messages1",
ResultMessages.warning().add("w.ex.an.2001")); // (1)
model.addAttribute("messages2",
ResultMessages.error().add("e.ex.an.9001")); // (2)
return "message/showMessages";
}
(1) Add ResultMessages of “warning” message type to Model with attribute name
“messages1”.
(2) Add ResultMessages of “info” message type to Model with attribute name
“messages2”.
• JSP (WEB-INF/views/message/showMessages.jsp)
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Result Message Example</title>
<style type="text/css">
.alert {
margin-bottom: 15px;
padding: 10px;
border: 1px solid;
border-radius: 4px;
text-shadow: 0 1px 0 #ffffff;
}
.alert-info {
background: #ebf7fd;
color: #2d7091;
border-color: rgba(45, 112, 145, 0.3);
}
.alert-warning {
background: #fffceb;
color: #e28327;
border-color: rgba(226, 131, 39, 0.3);
}
.alert-error {
background: #fff1f0;
color: #d85030;
border-color: rgba(216, 80, 48, 0.3);
}
</style>
</head>
<body>
<h1>Result Message</h1>
<h2>Messages1</h2>
<t:messagesPanel messagesAttributeName="messages1" /><!-- (1) -->
<h2>Messages2</h2>
<t:messagesPanel messagesAttributeName="messages2" /><!-- (2) -->
</body>
</html>
org.terasoluna.gfw.common.exception.BusinessException and
org.terasoluna.gfw.common.exception.ResourceNotFoundException stores
ResultMessages internally.
When displaying the business exception message, BusinessException wherein ResultMessages is set
should be thrown in Service class.
Catch BusinessException in Controller class and add the result message fetched from the caught exception
to Model.
• Service class
@Service
@Transactional
public class UserServiceImpl implements UserService {
// omitted
// omitted...
if (...) {
// illegal state!
ResultMessages messages = ResultMessages.error()
.add("e.ex.an.9001"); // (1)
throw new BusinessException(messages);
}
}
• Controller class
try {
userService.create(user);
} catch (BusinessException e) {
ResultMessages messages = e.getResultMessages(); // (1)
model.addAttribute(messages);
return "user/createForm";
}
// omitted
}
Normally, this method should be used to display error message instead of creating ResultMessages object in
Controller.
import org.terasoluna.gfw.common.message.ResultMessageType;
@Override
public String getType() { // (2)
return this.type;
}
@Override
public String toString() {
return this.type;
}
}
(1) Define Enum wherein ResultMessageType interface is implemented. A new message type can
be created using constant class; however it is recommended to create it using Enum.
(2) Return value of getType corresponds to class name of CSS which is output.
4.6.4 Appendix
<t:messagesPanel> tag contains various attributes for changing the display format.
.error,.notice,.success {
padding: .8em;
margin-bottom: 1.6em;
border: 2px solid #ddd;
}
.error {
background: #FBE3E4;
color: #8a1f11;
border-color: #FBC2C4;
}
.notice {
background: #FFF6BF;
color: #514721;
border-color: #FFD324;
}
.success {
background: #E6EFC2;
color: #264409;
border-color: #C6D880;
}
<div class="error">
<ul>
<li>There are inconsistencies in the data.</li>
</ul>
</div>
When you do not want to use <ul> tag to display the message list, it can be customized using outerElement
attribute and innerElement attribute.
(1) Set <span> tag which is a child element of “alert” class to Block-level element.
• jsp
• properties
i.ex.od.0001 = Please confirm order content. <font style="color: red; font-size: 16px;">If th
• Output image
When disableHtmlEscape attribute is false(default), the output will be as follows after HTML escaping.
Apart from ResultMessages object, <t:messagesPanel> tag can also output the following objects.
• java.lang.String
• java.lang.Exception
• java.util.List
For example, at the time of authentication error, Spring Security sets the exception class with attribute name
“SPRING_SECURITY_LAST_EXCEPTION”
in the request scope.
Perform the following settings if you want to output this exception message in <t:messagesPanel> tag
similar to the result messages.
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
<style type="text/css">
/* (1) */
.alert {
margin-bottom: 15px;
padding: 10px;
border: 1px solid;
border-radius: 4px;
text-shadow: 0 1px 0 #ffffff;
}
.alert-error {
background: #fff1f0;
color: #d85030;
border-color: rgba(216, 80, 48, 0.3);
}
</style>
</head>
<body>
<c:if test="${param.containsKey('error')}">
<t:messagesPanel messagesType="error"
messagesAttributeName="SPRING_SECURITY_LAST_EXCEPTION" /><!-- (2) -->
</c:if>
<form:form
action="${pageContext.request.contextPath}/authentication"
method="post">
<fieldset>
<legend>Login Form</legend>
<div>
<label for="username">Username: </label><input
type="text" id="username" name="username">
</div>
<div>
<label for="username">Password:</label><input
type="password" id="password" name="password">
</div>
<div>
<input type="submit" value="Login" />
</div>
</fieldset>
</form:form>
</body>
</html>
(2) In messagesAttributeName attribute, specify the attribute name wherein Exception object is
stored.
Unlike the ResultMessages object, it does not contain the information of message type; hence
it is necessary to explicitly specify the message type in messagesType attribute.
In all earlier examples, message keys were hard-coded strings; however it is recommended that you define the
message keys in constant class.
This section introduces the program that auto-generates message key constant class from properties file and the
corresponding usage method. You can customize and use them based on the requirements.
package com.example.common.message;
Next, create MessageKeysGen class in the same package as MessageKeys class and write the
logic as follows:
package com.example.common.message;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.regex.Pattern;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
try {
String line;
while ((line = br.readLine()) != null) {
String[] vals = line.split("=", 2);
if (vals.length > 1) {
String key = vals[0].trim();
String value = vals[1].trim();
pw.println(" /** " + key + "=" + value + " */");
pw.println(" public static final String "
+ key.toUpperCase().replaceAll(Pattern.quote("."),
"_").replaceAll(Pattern.quote("-"), "_")
+ " = \"" + key + "\";");
}
}
pw.println("}");
pw.flush();
} finally {
IOUtils.closeQuietly(br);
IOUtils.closeQuietly(pw);
}
}
}
package com.example.common.message;
/**
* Message Id
*/
public class MessageKeys {
/** i.ex.an.0001={0} upload completed. */
public static final String I_EX_AN_0001 = "i.ex.an.0001";
/** w.ex.an.2001=The recommended change interval has passed password. Please change y
public static final String W_EX_AN_2001 = "w.ex.an.2001";
/** e.ex.an.8001=Cannot upload, Because the file size must be less than {0}MB. */
public static final String E_EX_AN_8001 = "e.ex.an.8001";
/** e.ex.an.9001=There are inconsistencies in the data. */
public static final String E_EX_AN_9001 = "e.ex.an.9001";
}
4.7 Internationalization
4.7.1 Overview
Internationalization is a process wherein the display of labels and messages in an application is not fixed to a
specific language. It supports multiple languages. The language switching can be achieved by specifying a unit
called “Locale” expressing language, country and region.
• Text elements on the screen(code name, messages, labels of GUI components etc.) should be retrieve from
external definitions such as properties file. (should not be hard-coding in the source code)
• Using standard request header (specify the locale by language settings of browsers)
Note: When the error screen is to be internationalised, transition to error screen is performed by using MVC
Controller of Spring. If a direct transition to error screen is performed without Spring MVC, it may happen that
the message is not output in intended language.
Tip: The most commonly known abbreviation of internationalization is i18n. Internationalization is abbreviated
as i18n because the number of letters between the first “i” and the last “n” is 18 i.e. “nternationalizatio”.
To internationalize the messages on the screen, use the one of following MessageSourceimelementations for
managing messages.
• org.springframework.context.support.ResourceBundleMessageSource
• org.springframework.context.support.ReloadableResourceBundleMessageSource
applicationContext.xml
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>i18n/application-messages</value> <!-- (1) -->
</list>
</property>
</bean>
• Make sure you create application-messages.properties. If it does not exist, messages cannot
be fetched from MessageSource and JspTagException occurs while setting the messages in JSP.
When creating property files as above, it is determined which to use the file as follows:
• When properties file does not exist corresponding to the locale resolved by LocaleResolver,
application-messages.properties is used as default. (“_XX” portion does not exist in file
name)
Note: The locate to use is determined in the following order until a properties file is found corresponding to the
locale.
2. Locale specified by JVM on which application server runs (it may not be set in some cases)
It is frequently misunderstood that default properties file is used when properties file does not exist corresponding
to the locale sent from clients . In this case, then it is checked whether the file is available corresponding to the
locale specified by the application server. If not found, finally the default properties file is used.
Settings of AcceptHeaderLocaleResolver
spring-mvc.xml
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" /> <!-- (1) -->
(1) Specify
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver in id
attribute “localeResolver” of bean tag.
If this LocaleResolver is used, HTTP header “accept-language” is added for each request and
locale gets specified.
Definition of messages
application-messages.properties
application-messages_ja.properties
Implementation of JSP
(1) When message is to be output in JSP, it is output using Spring tag library; hence custom tag needs to
be defined.
<%@taglib uri="http://www.springframework.org/tags"
prefix="spring"%> should be defined.
Note: For details on common jsp files to be included, refer to Creating common JSP for include.
(2) Output the message using <spring:message> which is a Spring tag library of JSP.
In code attribute, set the key specified in properties.
In this example, if locale is ja, “管理画面 Top” is output and for other locales, “Admin Top” is output.
The method of dynamic changing the locale depending on screen operations etc. is effective in case of selecting a
specific language irrespective of user terminal (browser) settings.
LocaleChangeInterceptoris an interceptor to save the locale value specified by the request parameter
using org.springframework.web.servlet.LocaleResolver.
org.springframework.web.servlet.i18n.CookieLocaleResolver
2.
Save in client(using Cookie)
If switching the locale using the request parameter, use the LocaleChangeInterceptor.
spring-mvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <!-- (1) -->
</bean>
<!-- omitted -->
</mvc:interceptor>
</mvc:interceptors>
<bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/> <!-- (2) -->
</bean>
(2) In paramNameproperty, specify the name of request parameter. In this example, it is “request
URL?lang=xx”.
When paramName property is omitted, “locale” gets set. With “request URL?locale=xx”, it
becomes enabled.
spring-mvc.xml
(1) Define id attribute of bean tag in “localeResolver” and specify the class wherein
org.springframework.web.servlet.LocaleResolver is implemented.
In this example,
org.springframework.web.servlet.i18n.SessionLocaleResolver that stores
locale in session is specified.
id attribute of bean tag should be set as “localeResolver”.
By performing these settings, SessionLocaleResolver will be used at the
LocaleChangeInterceptor.
(2) Specify Locale in defaultLocale property. When Locale cannot be fetched from the session,
setup value of value becomes valid.
Note: When defaultLocale property is omitted, Locale set in the user terminal (browser) be-
comes valid.
spring-mvc.xml
(2) Specify Locale in defaultLocale property. When Locale cannot be fetched from Cookie, setup
value of value becomes valid.
Note: When defaultLocale property is omitted, Locale configured in the user terminal (browser)
becomes valid.
(3) The value specified in cookieName property is used as cookie name. If not specified, the value of
org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALEis
used as default. It is recommended to change not to tell the user explicitly Spring Framework is
used.
Messages settings
application-messages.properties
application-messages_ja.properties
Implementation of JSP
Tip:
• For details on common jsp files to be included, refer to Creating common JSP for include.
4.8 Codelist
4.8.1 Overview
A codelist is a pair comprising of “Code values (Value) and their display names (Label)”.
It is used for mapping code values with the labels to be displayed on screen such as selectbox.
• Functionality to read and cache the codelist defined in xml file or DB at the time of launching the applica-
tion.
• internationalization of codelist
This section describes settings for various codelists and their implementation methods.
• Using SimpleMapCodeList
• Using NumberRangeCodeList
• Using JdbcCodeList
Using SimpleMapCodeList
SimpleMapCodeList image
Once the bean definition file for codelist is created, it should be imported to already existing bean definition file.
By using the interceptor of common library, codelist can be set automatically in request scope and can be easily
referred from JSP.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /> <!-- (1) -->
<bean
class="org.terasoluna.gfw.web.codelist.CodeListInterceptor"> <!-- (2) -->
<property name="codeListIdPattern" value="CL_.+" /> <!-- (3) -->
</bean>
</mvc:interceptor>
</mvc:interceptors>
(3) Set the beanID pattern of codelist which is automatically set in the request scope.
In pattern, regular expression used in java.util.regex.Pattern should be set.
In the above example, only the data in which id is defined in “CL_XXX” format is targeted. In that
case, bean definition wherein id does not start with “CL_” should not be imported.
beanID defined in “CL_” can be used in JSP since it is set in the request scope.
<form:select path="orderStatus">
<form:option value="" label="--Select--" /> <!-- (4) -->
<form:options items="${CL_ORDERSTATUS}" /> <!-- (5) -->
</form:select>
(4) When setting dummy value at the top of the selectbox, null characters should be specified in the value.
Output HTML
<option value="1">Received</option>
<option value="2">Sent</option>
<option value="3">Cancelled</option>
</select>
Output screen
When using the codelist in Java class, inject the codelist by setting javax.inject.Inject annotation and
javax.inject.Named annotation. Specify the codelist name in @Named annotation.
import javax.inject.Named;
import org.terasoluna.gfw.common.codelist.CodeList;
@Inject
@Named("CL_ORDERSTATUS")
CodeList orderStatusCodeList; // (1)
Using NumberRangeCodeList
Image of NumberRangeCodeList
Tip: NumberRangeCodeList supports only Arabic numbers and does not support Chinese and Roman numbers.
Chinese and Roman numbers can be supported by using JdbcCodeList and SimpleMapCodeList.
1. In order to set From value < To value, the values increased in accordance with the interval are set in From-To
range in ascending order.
2. In order to set To value < From value, the values decreased in accordance with the interval are set in
To-From range in descending order.
The information here describes how to configure the ascending NumberRangeCodeList. For how to create
the descending NumberRangeCodeList or change interval, refer to “Variations of NumberRangeCodeList”. |
<bean id="CL_MONTH"
class="org.terasoluna.gfw.common.codelist.NumberRangeCodeList"> <!-- (1) -->
<property name="from" value="1" /> <!-- (2) -->
<property name="to" value="12" /> <!-- (3) -->
<property name="valueFormat" value="%d" /> <!-- (4) -->
<property name="labelFormat" value="%02d" /> <!-- (5) -->
<property name="interval" value="1" /> <!-- (6) -->
</bean>
(2) Specify the range start value. When omitted, “0” is set as range start value.
(4) Specify the format of the code value. Format used should be java.lang.String.format.
When omitted, “%s” is set.
(5) Specify the format of the code name. Format used should be java.lang.String.format.
When omitted, “%s” is set.
For details on settings shown below, refer to Using codelist in JSP described earlier.
Output HTML
<option value="6">06</option>
<option value="7">07</option>
<option value="8">08</option>
<option value="9">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
Output screen
For details on settings shown below, refer to Using codelist in Java class described earlier.
Using JdbcCodeList
If you want to reduce the read time for the startup, it is preferable to set an upper limit on the number of
acquisitions.
JdbcCodeList consists of a field which sets the
org.springframework.jdbc.core.JdbcTemplate.
If an upper limit is set for the fetchSize of JdbcTemplate, the records only till the upper limit are read at
the startup.
The fetched values can be changed dynamically by reloading. For details, refer to When reloading the codelist.
JdbcCodeList image
Definition of Table(authority)
authority_id authority_name
01 STAFF_MANAGEMENT
02 MASTER_MANAGEMENT
03 STOCK_MANAGEMENT
04 ORDER_MANAGEMENT
05 SHOW_SHOPPING_CENTER
<bean id="AbstractJdbcCodeList"
class="org.terasoluna.gfw.common.codelist.JdbcCodeList" abstract="true"> <!-- (3) -->
<property name="jdbcTemplate" ref="jdbcTemplateForCodeList" /> <!-- (4) -->
</bean>
(6) Write an SQL to be fetched in querySql property. At that time, always specify “ORDER BY” clause
and determine the order.
If “ORDER BY” is not specified, the order gets changed while fetching the records.
(7) Set the value corresponding to the Key of Map in valueColumn property. In this example,
authority_id is set.
For details on settings shown below, refer to Using codelist in JSP described earlier.
<form:checkboxes items="${CL_AUTHORITIES}"/>
Output HTML
<span>
<input id="authorities1" name="authorities" type="checkbox" value="01"/>
<label for="authorities1">STAFF_MANAGEMENT</label>
</span>
<span>
<input id="authorities2" name="authorities" type="checkbox" value="02"/>
<label for="authorities2">MASTER_MANAGEMENT</label>
</span>
<span>
<input id="authorities3" name="authorities" type="checkbox" value="03"/>
<label for="authorities3">STOCK_MANAGEMENT</label>
</span>
<span>
<input id="authorities4" name="authorities" type="checkbox" value="04"/>
<label for="authorities4">ORDER_MANAGEMENT</label>
</span>
<span>
<input id="authorities5" name="authorities" type="checkbox" value="05"/>
<label for="authorities5">SHOW_SHOPPING_CENTER</label>
</span>
Output screen
For details on settings shown below, refer to Using codelist in Java class described earlier.
Note: In case of handling codelist in applications that match with the following conditions, it should be analyzed
if the codelist label can be stored in Enum class using EnumCodeList . By storing codelist label in Enum class,
the information and operations linked with code values can be aggregated in Enum class.
• It is necessary to store the code values in Enum class (i.e. the process needs to be performed considering
code values in Java logic)
In case of using EnumCodeList, create Enum class that implements EnumCodeList.CodeListItem in-
terface. Example is shown below.
package com.example.domain.model;
import org.terasoluna.gfw.common.codelist.EnumCodeList;
// (2)
RECEIVED ("1", "Received"),
SENT ("2", "Sent"),
CANCELLED ("3","Cancelled");
// (3)
private final String value;
private final String label;
// (4)
private OrderStatus(String codeValue, String codeLabel) {
this.value = codeValue;
this.label = codeLabel;
}
// (5)
@Override
public String getCodeValue() {
return value;
}
// (6)
@Override
public String getCodeLabel() {
return label;
}
Define constants.
When creating constants, specify the information (code values and labels) required for creating a
(2)
codelist.
In above example, following 3 constants are defined.
• RECEIVED (code value="1", label="Received")
• SENT (code value="2", label="Sent")
• CANCELLED (code value="3", label="Cancelled")
Note: Sorting order of codelist when using EnumCodeList will be the order of defining constants.
Create a property to store the information (code values and labels) required for creating a codelist.
(3)
Create a constructor to receive the information (code values and labels) required for creating a codelist.
(4)
EnumCodeList is defined in bean definition file for codelist. Example of definition is shown below.
<bean id="CL_ORDERSTATUS"
class="org.terasoluna.gfw.common.codelist.EnumCodeList"> <!-- (7) -->
<constructor-arg value="com.example.domain.model.OrderStatus" /> <!-- (8) -->
</bean>
(7)
For details on how to use codelist in JSP, refer to Using codelist in JSP described earlier.
For details on how to use codelist in Java class, refer to Using codelist in Java class described earlier.
SimpleI18nCodeList image
It is easier to understand if you consider SimpleI18nCodeList as two dimensional table wherein row is
Locale, column contains code values and cell details are labels.
row=Locale,column=Code
0 10000 20000 30000 40000 50000
en unlimited Less than Less than Less than Less than Less tha
\10,000 \20,000 \30,000 \40,000 \50,000
ja 上限なし 10,000 円以下 20,000 円以下 30,000 円以下 40,000 円以下 50,000 円以下
For creating a codelist table that supports internationalization, SimpleI18nCodeList has been set in following
3 ways.
• Set java.util.Map(key = code value, value = label) for each locale by rows.
• Set java.util.Map(key = locale, value = label) for each code value by columns.
It is recommended that you set the codelist using “Set CodeList for each locale by rows.” method.
The way of setting the CodeList for each locale by rows considering the above example of selectbox for select-
ing charges, is mentioned below. For other setting methods, refer to Setting SimpleI18nCodeList.
<bean id="CL_I18N_PRICE"
class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
<property name="rowsByCodeList"> <!-- (1) -->
<util:map>
<entry key="en" value-ref="CL_PRICE_EN" />
<entry key="ja" value-ref="CL_PRICE_JA" />
</util:map>
</property>
</bean>
Definition of Bean definition file(xxx-codelist.xml) when creating SimpleMapCodeList for each locale
<bean id="CL_I18N_PRICE"
class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
<property name="rowsByCodeList">
<util:map>
<entry key="en" value-ref="CL_PRICE_EN" />
<entry key="ja" value-ref="CL_PRICE_JA" />
</util:map>
</property>
</bean>
(2) For bean definition CL_PRICE_EN where locale is “en”, codelist class is set in
SimpleMapCodeList.
(3) For bean definition CL_PRICE_JA where locale is “ja”, codelist class is set in
SimpleMapCodeList.
Definition of Bean definition file(xxx-codelist.xml) when creating JdbcCodeList for each locale
<bean id="CL_I18N_PRICE"
class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
<property name="rowsByCodeList">
<util:map>
<entry key="en" value-ref="CL_PRICE_EN" />
<entry key="ja" value-ref="CL_PRICE_JA" />
</util:map>
</property>
</bean>
(4) For bean definition CL_PRICE_EN where locale is “en”, codelist class is set in JdbcCodeList.
(5) For bean definition CL_PRICE_JA where locale is “ja”, codelist class is set in JdbcCodeList.
en 0 unlimited
ja 0 上限なし
Warning: Currently SimpleI18nCodeList does not support reloadable functionality. It should be noted
that even if JdbcCodeList (reloadable CodeList) referred by SimpleI18nCodeList is reloaded, it
does not get reflected in SimpleI18nCodeList. In order to make it reloadable, it should be implemented
independently. For implementation method, refer to Customizing the codelist independently.
Description of basic settings is omitted since it is same as Using codelist in JSP described earlier.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean
class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
<property name="fallbackTo" value="en" /> <!-- (1) -->
</bean>
</mvc:interceptor>
</mvc:interceptors>
Description of basic settings is omitted since it is same as Using codelist in Java class described earlier.
@RequestMapping("orders")
@Controller
public class OrderController {
@Inject
@Named("CL_I18N_PRICE")
I18nCodeList priceCodeList;
// ...
When it is necessary to refer the codelist in JSP, it can be referred same as java.util.Map interface.
(1) Get a codelist that has been converted to the java.util.Map from the request scope (In this
example, "CL_ORDERSTATUS" used as codelist ). The codelist has been referred with the beanID
of codelist.
Then specify a code value as a key of the Map interface which displays a corresponding code name
(In this example, orderStatus value is used as a key).
When checking whether the input value is the code value defined in codelist,
org.terasoluna.gfw.common.codelist.ExistInCodeList annotation for BeanValidation is
provided in common library.
For details on BeanValidation and message output method, refer to Input Validation.
For input validation using @ExistInCodeList annotation, it is necessary to carry out “Definition of error
messages” for @ExistInCodeList .
Note: In the terasoluna-gfw-common 5.0.0.RELEASE or later, the format of message property key has been
changed to standard format of Bean Validation (FQCN of annotation + .message).
version 1.0.x.RELEASE
org.terasoluna.gfw.common.codelist.ExistInCodeList
For migrating to the version 5.0.0.RELEASE or later from the version 1.0.x.RELEASE, when message is changed
For migrating to version 1.0.2.RELEASE or later from version 1.0.1.RELEASE or prior, if the message defined
in ValidationMessages.propertiesincluded in jar of terasoluna-gfw-common, is used, it is necessary
to define the message by creating ValidationMessages.properties.
Form object
(1) Set @ExistInCodeList annotation for the field for which input is to be validated,
and specify the target codelist in codeListId.
As a result of above settings, when characters other than M, F are stored in gender, the system throws an error.
Tip: @ExistInCodeList input validation supports only the implementation class (String etc) of
CharSequence interface or Character type. Therefore, even if the fields with @ExistInCodeListmay
contain integer values, they should be defined as Stringdata type. (such as Year/Month/Day)
Codelist provided in common library is read at the time of launching the application and it is never updated
subsequently. However, in some cases, when the master data of the codelist is updated, the codelist also needs to
be updated.
This guideline recommends the method to reload the codelist periodically using Spring Task Scheduler.
However, when it is necessary to arbitrarily refresh the codelist, it is appropriate to call refresh method in Con-
troller class.
Note: For the codelist having ReloadableCodeList interface, refer to List of codelist types.
(1) Specify the thread pool size in pool-size attribute of <task:scheduler> element.
When pool-size attribute is not specified, the value is set to “1”.
See the example below for directly calling refresh method of JdbcCodeList in Service class.
Controller class
@Controller
@RequestMapping(value = "codelist")
public class CodeListContoller {
@Inject
CodeListService codeListService; // (1)
(1) Inject the Service class that executes refresh method of ReloadableCodeList class.
(2) Execute the refresh method of Service class that executes refresh method of ReloadableCodeList
class.
Service class
The description below is given only for the implementation class. Description for interface class has been omitted.
@Service
public class CodeListServiceImpl implements CodeListService { // (3)
@Inject
@Named(value = "CL_AUTHORITIES") // (4)
ReloadableCodeList codeListItem; // (5)
@Override
public void refresh() { // (6)
codeListItem.refresh(); // (7)
}
}
(4) Specify the corresponding codelist using @Named annotation at the time of injecting the codelist.
ID of the bean to be fetched should be specified in value attribute.
Codelist of ID attribute “CL_AUTHORITIES” of bean tag defined in Bean definition file is injected.
In order to create a codelist which does not fall under the 4 types provided by the common library, the existing
codelist can be customized independently. Refer to the table below for the implementation method and type of
codelist that can be created.
Actual example of independent customization is shown below. It illustrates a codelist for creating a list of current
year and the next year. (Example: If current year is 2013, it is stored in codelist in the order of “2013, 2014”.)
Codelist class
@Component("CL_YEAR") // (1)
public class DepYearCodeList extends AbstractCodeList { // (2)
@Inject
JodaTimeDateFactory dateFactory; // (3)
@Override
public Map<String, String> asMap() { // (4)
DateTime dateTime = dateFactory.newDateTime();
DateTime nextYearDateTime = dateTime.plusYears(1);
return Collections.unmodifiableMap(depYearMap);
}
}
(4) Override asMap() method and create the list of current year and next year.
Implementation differs with every created codelist.
(5) "CL_YEAR" registered as component in items attribute should be specified in ${} placeholder to
fetch the corresponding codelist.
Output HTML
<option value="2014">2014</option>
</select>
Output screen
Note: Implementation should be made thread-safe at the time of customizing the reloadable CodeList indepen-
dently.
4.8.4 Appendix
Setting SimpleI18nCodeList
Apart from the settings mentioned in How to use SimpleI18nCodeList, SimpleI18nCodeList can be set in following
2 ways. The respective setting methods are explained using the example of selectbox for selecting charges.
Set java.util.Map (key = code value, value = label) for each locale by rows
<bean id="CL_I18N_PRICE"
class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
<property name="rows"> <!-- (1) -->
<util:map>
<entry key="en">
<util:map>
<entry key="0" value="unlimited" />
<entry key="10000" value="Less than \\10,000" />
<entry key="20000" value="Less than \\20,000" />
<entry key="30000" value="Less than \\30,000" />
<entry key="40000" value="Less than \\40,000" />
<entry key="50000" value="Less than \\50,000" />
</util:map>
</entry>
<entry key="ja">
<util:map>
<entry key="0" value="unlimited" />
<entry key="10000" value="10,000 円以下" />
(1) Set “Map of Map” for rows property. External Map key is java.lang.Locale.
Internal Map key is a code value and value is a label corresponding to locale.
Set java.util.Map(key = locale, value = label) for each code value by columns
<bean id="CL_I18N_PRICE"
class="org.terasoluna.gfw.common.codelist.i18n.SimpleI18nCodeList">
<property name="columns"> <!-- (1) -->
<util:map>
<entry key="0">
<util:map>
<entry key="en" value="unlimited" />
<entry key="ja" value="上限なし" />
</util:map>
</entry>
<entry key="10000">
<util:map>
<entry key="en" value="Less than \\10,000" />
<entry key="ja" value="10,000 円以下" />
</util:map>
</entry>
<entry key="20000">
<util:map>
<entry key="en" value="Less than \\20,000" />
<entry key="ja" value="20,000 円以下" />
</util:map>
</entry>
<entry key="30000">
<util:map>
(1) Set “Map of Map” for columns property. External Map key is a code value.
Internal Map key is java.lang.Locale and value is a label corresponding to locale.
Variations of NumberRangeCodeList
<bean id="CL_BIRTH_YEAR"
class="org.terasoluna.gfw.common.codelist.NumberRangeCodeList">
<property name="from" value="2013" /> <!-- (1) -->
<property name="to" value="2000" /> <!-- (2) -->
</bean>
(1) Specify the range start value. Specify a value greater than the one specified in “value” attribute of “to”
property.
As per this specification, display the values decreased in accordance with the interval in To-From
range in descending order.
Since interval is not set, default value 1 is applied.
Output HTML
Output screen
<bean id="CL_BULK_ORDER_QUANTITY_UNIT"
class="org.terasoluna.gfw.common.codelist.NumberRangeCodeList">
<property name="from" value="10" />
<property name="to" value="50" />
<property name="interval" value="10" /> <!-- (1) -->
</bean>
(1) Specify increment (decrement) value. Then, store the values obtained upon increasing (decreasing)
the interval value within From-To range as codelist.
In the above example, the values are stored in the order of 10,20,30,40,50 in the codelist.
Output HTML
<option value="50">50</option>
</select>
Output screen
Note: If From-To value exceeds the specified range, then the value increased (decreased) in accordance with
interval is not stored in the codelist.
<bean id="CL_BULK_ORDER_QUANTITY_UNIT"
class="org.terasoluna.gfw.common.codelist.NumberRangeCodeList">
<property name="from" value="10" />
<property name="to" value="55" />
<property name="interval" value="10" />
</bean>
5 values of 10,20,30,40,50 are stored in the codelist. The value of subsequent interval 60 and the range threshold
value 55 are not stored in the codelist.
4.9.1 Overview
Files are uploaded using the File Upload functionality supported by Servlet 3.0 and classes provided by Spring
Web.
Note: In this chapter, File Upload functionality supported by Servlet 3.0 is used; hence, Servlet version
3.0 or above is a prerequisite here.
Note: File Upload functionality of Servlet 3.0 may likely result into garbling of multi byte characters of
file names or request parameters on some application server.
When the Application Server wherein problems are likely to occur is to be used, using Commons FileU-
pload can help in avoiding such problems. For settings to use Commons FileUpload, refer to “File upload
using Commons FileUpload”.
Application server for which a occurrence of problem is confirmed at the time of version 5.0.1.RELEASE
is as given below.
• WebLogic 12.1.3
Basic flow of uploading files using File Upload functionality supported by Servlet 3.0, and classes of Spring Web,
is as shown below.
Note: Controller performs the process for MultipartFile object of Spring Web; hence implementa-
tion which is dependent on the File Upload API provided by Servlet 3.0 can be excluded.
1.
org.springframework.web.multipart. Interface indicating uploaded file.
MultipartFile It plays a role in abstraction of file objects handled
by the File Upload functionality to be used.
2.
MultipartFile class of File Upload
org.springframework.web.multipart.support.
functionality introduced through Servlet 3.0.
StandardMultipartHttpServletRequest$
Process is delegated to the Part object introduced
StandardMultipartFile through Servlet 3.0.
3.
org.springframework.web.multipart. Interface that resolves the analysis method of
MultipartResolver multipart/form-data request.
It plays a role in generating MultipartFile
object corresponding to implementation of File
Upload functionality.
4.
MultipartResolver class for File Upload
org.springframework.web.multipart.support.
functionality introduced through Servlet 3.0.
StandardServletMultipartResolver
5.
A class which generates MultipartFile by calling a
org.springframework.web.multipart.support.
class which implements MultipartResolver from DI
MultipartFilter container, at the time of multipart/form-data request.
If this class is not used, a request parameter cannot
be fetched in Servlet Filter process when maximum
size allowed in file upload exceeds the limit.
Therefore, it is recommended to use MultipartFilter
in this guideline.
Tip: In this guideline, it is a prerequisite to use File Upload functionality implemented from Servlet
3.0. However, Spring Web also provides an implementation class for “Apache Commons FileUpload”.
Application settings
• 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/
version="3.0"> <!-- (1) (2) -->
<servlet>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- omitted -->
<multipart-config> <!-- (3) -->
<max-file-size>5242880</max-file-size> <!-- (4) -->
<max-request-size>27262976</max-request-size> <!-- (5) -->
<file-size-threshold>0</file-size-threshold> <!-- (6) -->
</multipart-config>
</servlet>
</web-app>
(1) Specify the XSD file of Servlet 3.0 or above in xsi:schemaLocation attribute of
<web-app> element.
(2) Specify version 3.0 or above in the version attribute of <web-app> element.
(3) Add <multipart-config> element to <servlet> element of the Servlet using the File
Upload functionality.
(6) Specify the threshold value (number of bytes for 1 file) if the contents of uploaded file are to be
saved as a temporary file.
4.9. File Upload 721
If this parameter is not specified explicitly, there are application servers wherein values
specified for elements <max-file-size> and <max-request-size> are considered
invalid; hence default value (0) is being specified explicitly.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: In order to increase the resistance against Dos attack, max-file-size and
max-request-size should be specified without fail.
For Dos attack, refer to Dos attack with respect to upload functionality.
Note:
Uploaded file is by default output as temporary file. However, its output can be controlled using
the configuration value of <file-size-threshold> element, which is the child element of
<multipart-config>.
<multipart-config>
<!-- omitted -->
<file-size-threshold>32768</file-size-threshold> <!-- (7) -->
</multipart-config>
(7) Specify the threshold file size (number of bytes of 1 file) if contents of uploaded file are
to be saved as a temporary file.
If not specified, 0 is set.
If uploaded file size exceeds the specified value,
it is output as a temporary file to the disk and deleted when the request is completed.
Warning: This parameter shows a trade-off relationship as indicated by the following points.
Hence, configuration value corresponding to system characteristics should be specified..
• Increasing the configuration value improves processing performance as, processing
gets completed within available memory. However, there is a high possibility that
OutOfMemoryError may occur due to Dos attack.
• If configuration value is reduced, memory utilization can be controlled to the minimum,
thereby avoiding the possibility of OutOfMemoryError due to Dos attack etc. How-
ever, there is a high possibility of performance degradation since the frequency of disk
IO generation is high.
To change output directory of temporary files, specify directory path in <location> element, which is
the child element of <multipart-config>.
<multipart-config>
<location>/tmp</location> <!-- (8) -->
<!-- omitted -->
</multipart-config>
Warning: The directory specified in <location> element is the one used by the application
server (servlet container) and cannot be accessed from application.
When the files uploaded as application are to be saved as temporary files, they should be output to
a directory other than the directory specified in <location> element.
The operation when the maximum size allowed in file upload exceeds the limit at the time of multipart/form-data
request, varies depending on the application server. MultipartException generated when maximum size
exceeds the limit depending on the application server is likely to be not detected and exception handling settings
described later will be invalid.
Since this operation can be evaded by setting MiltipartFilter, MiltipartFilter setting is described
as a prerequisite in this guideline.
Setting example is given below.
• web.xml
Warning: Precautions when maximum size limit for file upload is exceeded
When allowable size limit for file upload has been exceeded, an ‘Over the size limit” error may get
detected before fetching a CSRF token in some of the application servers like WebLogic and CSRF
token check is not performed.
Add the exception handling definition of MultipartException which occurs when a request for file or mul-
tipart with non-permissible size is sent.
MultipartException is an exception caused due to file size specified by the client; hence it is
recommended to handle it as a client error (HTTP response code=4xx).
If exception handling is not added for individual exception, it is eventually treated as system error; hence
make sure that it is defined without fail.
Settings for handling MultipartException differ depending upon whether MultipartFilter is used or
not.
In case of using MultipartFilter, exception handling is carried out by using the <error-page>
functionality of servlet container.
Example of settings is shown below.
• web.xml
<error-page>
<!-- (1) -->
<exception-type>org.springframework.web.multipart.MultipartException</exception-type>
<!-- (2) -->
<location>/WEB-INF/views/common/error/fileUploadError.jsp</location>
</error-page>
• fileUploadError.jsp
</html>
When not using MultipartFilter, carry out exception handling by using SystemExceptionResolver.
Example of settings is shown below.
• spring-mvc.xml
<bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
<!-- omitted -->
<property name="exceptionMappings">
<map>
<!-- omitted -->
<!-- (4) -->
<entry key="MultipartException"
value="common/error/fileUploadError" />
</map>
</property>
<property name="statusCodes">
<map>
<!-- omitted -->
<!-- (5) -->
<entry key="common/error/fileUploadError" value="400" />
</map>
</property>
<!-- omitted -->
</bean>
(5) Add the definition of HTTP status code which is received as response when
MultipartException occurs.
Add exception code settings when setting an exception code for MultipartException.
Exception code is output to the log which is output using log output functionality of common library.
Exception code can also be referred from View (JSP).
For referring to exception code from View (JSP), refer to Method to display system exception code on screen.
• applicationContext.xml
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<property name="exceptionMappings">
<map>
<!-- (6) -->
<entry key="MultipartException" value="e.xx.fw.6001" />
<!-- omitted -->
</map>
</property>
<property name="defaultExceptionCode" value="e.xx.fw.9001" />
<!-- omitted -->
</bean>
Implementing form
// omitted
@NotNull
@Size(min = 0, max = 100)
private String description;
Implementing JSP
<form:form
action="${pageContext.request.contextPath}/article/upload" method="post"
modelAttribute="fileUploadForm" enctype="multipart/form-data"> <!-- (1) (2) -->
<table>
<tr>
<th width="35%">File to upload</th>
<td width="65%">
<form:input type="file" path="file" /> <!-- (3) -->
<form:errors path="file" />
</td>
</tr>
<tr>
<th width="35%">Description</th>
<td width="65%">
<form:input path="description" />
<form:errors path="description" />
</td>
</tr>
<tr>
<td> </td>
<td><form:button>Upload</form:button></td>
</tr>
</table>
</form:form>
(2) Specify attribute name of form object in the modelAttribute of <form:form> element.
In the above example, "fileUploadForm" is specified.
(3) Specify "file" in type attribute of <form:input> element and specify MultipartFile
property name in path attribute.
In the above example, the uploaded file is stored in "file" property of FileUploadForm
object.
Implementing Controller
@RequestMapping("article")
@Controller
public class ArticleController {
@Value("${upload.allowableFileSize}")
private int uploadAllowableFileSize;
// omitted
// (1)
@ModelAttribute
public FileUploadForm setFileUploadForm() {
return new FileUploadForm();
}
// (2)
@RequestMapping(value = "upload", method = RequestMethod.GET, params = "form")
public String uploadForm() {
return "article/uploadForm";
}
// (3)
@RequestMapping(value = "upload", method = RequestMethod.POST)
public String upload(@Validated FileUploadForm form,
BindingResult result, RedirectAttributes redirectAttributes) {
if (result.hasErrors()) {
return "article/uploadForm";
// (4)
if (!StringUtils.hasLength(uploadFile.getOriginalFilename())) {
result.rejectValue(uploadFile.getName(), "e.xx.at.6002");
return "article/uploadForm";
}
// (5)
if (uploadFile.isEmpty()) {
result.rejectValue(uploadFile.getName(), "e.xx.at.6003");
return "article/uploadForm";
}
// (6)
if (uploadAllowableFileSize < uploadFile.getSize()) {
result.rejectValue(uploadFile.getName(), "e.xx.at.6004",
new Object[] { uploadAllowableFileSize }, null);
return "article/uploadForm";
}
// (7)
// omit processing of upload.
// (8)
redirectAttributes.addFlashAttribute(ResultMessages.success().add(
"i.xx.at.0001"));
// (9)
return "redirect:/article/upload?complete";
}
// omitted
(1) Method of storing the form object for file upload in Model.
In the above example, the attribute name for storing form object in Model is
"fileUploadForm".
(8) As per the requirement, the processing result message notifying about successful upload is
When uploading files, it is recommended to perform transaction token check and screen transition based
on PRG pattern. With this, upload of same files caused due to double submission can be prevented.
For more details on how to prevent double submission, refer to Double Submit Protection.
Methods to operate the uploaded file are provided in MultipartFile. For details on using each method, refer
to JavaDoc of MultipartFile class.
In the above implementation example, uploaded file is validated as a Controller process. However, here the
uploaded file is validated using Bean Validation.
For validation details, refer to Input Validation.
Note: It is recommended to use Bean Validation since this makes maintenance of Controller processes
easier.
// (1)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileRequiredValidator.class)
public @interface UploadFileRequired {
String message() default "{com.examples.upload.UploadFileRequired.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// (2)
public class UploadFileRequiredValidator implements
ConstraintValidator<UploadFileRequired, MultipartFile> {
@Override
public void initialize(UploadFileRequired constraint) {
}
@Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext context) {
return multipartFile != null &&
StringUtils.hasLength(multipartFile.getOriginalFilename());
}
// (3)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileNotEmptyValidator.class)
public @interface UploadFileNotEmpty {
String message() default "{com.examples.upload.UploadFileNotEmpty.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// (4)
public class UploadFileNotEmptyValidator implements
ConstraintValidator<UploadFileNotEmpty, MultipartFile> {
@Override
public void initialize(UploadFileNotEmpty constraint) {
}
@Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext context) {
if (multipartFile == null ||
!StringUtils.hasLength(multipartFile.getOriginalFilename())) {
return true;
}
return !multipartFile.isEmpty();
}
(4) Create implementation class to verify that the file is not empty.
// (5)
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = UploadFileMaxSizeValidator.class)
public @interface UploadFileMaxSize {
String message() default "{com.examples.upload.UploadFileMaxSize.message}";
long value() default (1024 * 1024);
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
// (6)
public class UploadFileMaxSizeValidator implements
ConstraintValidator<UploadFileMaxSize, MultipartFile> {
@Override
public void initialize(UploadFileMaxSize constraint) {
this.constraint = constraint;
}
@Override
public boolean isValid(MultipartFile multipartFile,
ConstraintValidatorContext context) {
if (constraint.value() < 0 || multipartFile == null) {
return true;
}
return multipartFile.getSize() <= constraint.value();
}
(5) Create annotation to verify that the file size is within allowable range.
(6) Create implementation class to verify that the file size is within allowable range.
Implementing form
// omitted
// (7)
@UploadFileRequired
@UploadFileNotEmpty
@UploadFileMaxSize
private MultipartFile file;
@NotNull
@Size(min = 0, max = 100)
private String description;
Implementing Controller
// (8)
if (result.hasErrors()) {
return "article/uploadForm";
}
redirectAttributes.addFlashAttribute(ResultMessages.success().add(
"i.xx.at.0001"));
return "redirect:/article/upload";
}
The explanation that has already been covered under single file upload has been omitted to avoid duplication.
Implementing form
// (1)
public class FileUploadForm implements Serializable {
// omitted
@UploadFileRequired
@UploadFileNotEmpty
@UploadFileMaxSize
private MultipartFile file;
@NotNull
@Size(min = 0, max = 100)
private String description;
// omitted
@Valid // (2)
private List<FileUploadForm> fileUploadForms; // (3)
(1) Class that maintains the information of each file (uploaded file itself and related form fields).
In the above example, form object that was originally created to explain about single file
upload, is re-used.
(2) To carry out input validation through Bean Validation for the object maintained in list, assign
@Valid annotation.
(3) Define the object that maintains information of each file (uploaded file itself and related form
fields) as List property.
Note: When only files are to be uploaded, MultipartFile object can also be defined as List property;
however, for input validation of uploaded files using Bean Validation, there is better compatibility if the
object that maintains information of each file, is defined as List property.
Implementing JSP
<form:form
action="${pageContext.request.contextPath}/article/uploadFiles" method="post"
modelAttribute="filesUploadForm" enctype="multipart/form-data">
<table>
<tr>
<th width="35%">File to upload</th>
<td width="65%">
<form:input type="file" path="fileUploadForms[0].file" /> <!-- (1) -->
<form:errors path="fileUploadForms[0].file" />
</td>
</tr>
<tr>
<th width="35%">Description</th>
<td width="65%">
<form:input path="fileUploadForms[0].description" />
<form:errors path="fileUploadForms[0].description" />
</td>
</tr>
</table>
<table>
<tr>
<th width="35%">File to upload</th>
<td width="65%">
<form:input type="file" path="fileUploadForms[1].file" /> <!-- (1) -->
<form:errors path="fileUploadForms[1].file" />
</td>
</tr>
<tr>
<th width="35%">Description</th>
<td width="65%">
<form:input path="fileUploadForms[1].description" />
<form:errors path="fileUploadForms[1].description" />
</td>
</tr>
</table>
<div>
<form:button>Upload</form:button>
</div>
</form:form>
Implementing Controller
if (result.hasErrors()) {
return "article/uploadForm";
}
// (1)
for (FileUploadForm fileUploadForm : form.getFileUploadForms()) {
redirectAttributes.addFlashAttribute(ResultMessages.success().add(
"i.xx.at.0001"));
return "redirect:/article/upload?complete";
}
(1) Fetch MultipartFile from the object that maintains information of each file (uploaded file
itself and related form fields) and implement upload process.
The above example does not cover any specific implementation; however process to store the
file on a shared disk or database is performed.
The method to simultaneously upload multiple files using “multiple” attribute of input tag supported by HTML5,
is explained below.
The explanation that has already been covered under single file upload and multiple file upload has been omitted.
Implementing form
When uploading multiple files simultaneously using “multiple” attribute of HTML5 input tag, it is necessary to
receive collection of org.springframework.web.multipart.MultipartFile object by binding it
to form object.
// (1)
public class FilesUploadForm implements Serializable {
// omitted
// (2)
@UploadFileNotEmpty
private List<MultipartFile> files;
Implementing Validator
When carrying out input validation for multiple MultipartFile objects stored in collection, it is necessary to
implement Validator for Collection.
The section explains about creating Validator for Collection using the Validator created for single file.
// (1)
public class UploadFileNotEmptyForCollectionValidator implements
ConstraintValidator<UploadFileNotEmpty, Collection<MultipartFile>> {
// (2)
private final UploadFileNotEmptyValidator validator =
new UploadFileNotEmptyValidator();
// (3)
@Override
public void initialize(UploadFileNotEmpty constraintAnnotation) {
validator.initialize(constraintAnnotation);
}
// (4)
@Override
public boolean isValid(Collection<MultipartFile> values,
ConstraintValidatorContext context) {
for (MultipartFile file : values) {
if (!validator.isValid(file, context)) {
return false;
}
}
return true;
}
(1) Class for performing implementation to verify that none of the files is empty.
Specify Collection<MultipartFile> as the type of value to be verified.
(2) In order to delegate the actual process to a Validator for single file, create an instance for that
Validator.
// omitted
(5) Add the Validator class that carries out checks with respect to multiple files, to the annotation
used for verification.
Specify the class created in step (1) in the “validatedBy” attribute of @Constraint
annotation.
With this, the class created in step (1) is executed when validating the property with
@UploadFileNotEmpty annotation.
Implementing JSP
<form:form
action="${pageContext.request.contextPath}/article/uploadFiles" method="post"
modelAttribute="filesUploadForm2" enctype="multipart/form-data">
<table>
<tr>
<th width="35%">File to upload</th>
<td width="65%">
<form:input type="file" path="files" multiple="multiple" /> <!-- (1) -->
<form:errors path="files" />
</td>
</tr>
</table>
<div>
<form:button>Upload</form:button>
</div>
</form:form>
(1) In “path” attribute, specify “multiple” attribute by indicating property name of form object.
By specifying “multiple” attribute, multiple files can be selected and uploaded using browser
supporting HTML5.
Implementing Controller
// (1)
for (MultipartFile file : form.getFiles()) {
redirectAttributes.addFlashAttribute(ResultMessages.success().add(
"i.xx.at.0001"));
return "redirect:/article/upload?complete";
}
(1) Implement upload process by fetching the list which stores MultipartFile objects from
form object.
The above example does not cover any specific implementation; however process to store the
file on a shared disk or database is performed.
Temporary upload
Temporary upload is required when a file is to be uploaded midway through screen transitions like upload result
confirmation screen etc.
Note: Contents of file stored in MultipartFile object may be deleted once the upload request is
completed. Therefore, when the file contents are to be handled across requests, these contents and meta
information (file name etc.) maintained in MultipartFile object need to be saved in a file or form.
The contents of file stored in MultipartFile object are deleted when step (3) of the following process-
ing flow is completed.
(1) On Input Screen, select the file to be uploaded and send a request for displaying Confirm
Screen.
(2) Controller temporarily saves contents of uploaded file in the temporary directory for
application.
(3) Controller returns View name of Confirm Screen and then displays the Confirm Screen.
(6) Service moves the temporary file saved in temporary directory to this directory or database.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Temporary upload process is the responsibility of application layer; hence it is executed by Con-
troller or Helper class.
Implementing Controller
Example for temporarily saving the uploaded file in a temporary directory, is shown below.
@Component
public class UploadHelper {
// (2)
@Value("${app.upload.temporaryDirectory}")
private File uploadTemporaryDirectory;
// (1)
public String saveTemporaryFile(MultipartFile multipartFile)
throws IOException {
// (2)
FileUtils.copyInputStreamToFile(multipartFile.getInputStream(),
uploadTemporaryFile);
return uploadTemporaryFileId;
}
// omitted
@Inject
UploadHelper uploadHelper;
if (result.hasErrors()) {
return "article/uploadForm";
}
// (3)
String uploadTemporaryFileId = uploadHelper.saveTemporaryFile(form
.getFile());
// (4)
form.setUploadTemporaryFileId(uploadTemporaryFileId);
form.setFileName(form.getFile().getOriginalFilename());
return "article/uploadConfirm";
}
(3) Call the Helper method to temporarily save the uploaded file.
In the above example, ID by which the temporarily saved file is identified, is returned as the
return value of Helper method.
(4) Save the meta information of uploaded file (ID by which the file is identified, file name etc.) in
form object.
In the above example, name of the uploaded file and ID by which the temporarily saved file is
identified, are stored in form object.
Note: Directory of temporary directories should be fetched from external properties as it may differ with
the environment in which the application is deployed. For details on external properties, refer to Properties
Management.
Warning: In the above example, it is a file saved temporarily on the local disk of application server.
However, when the application server is clustered, it needs to be saved in the database or on a shared
disk. As a result, it is necessary to design a storage destination by considering even the non-functional
requirements.
Transaction management is necessary in case of saving the file to the database. As a result, the process
to save it to the database will be delegated to Service method.
When uploading files using the temporary upload method, there is a possibility of unnecessary files piling up in
temporary directory.
The cases are as follows:
• When system error occurs during the screen operations after temporary upload
• When server stops during the screen operations after temporary upload etc ...
Warning: A mechanism should be provided to delete unnecessary files as the disk may run out of
space if such files are left to pile up.
This guideline explains about deleting unnecessary files using the “Task Scheduler” functionality provided by
Spring Framework. For details on “Task Scheduler”, refer to the official website “Task Execution and Scheduling”.
Note: Although this guideline explains about how to use “Task Scheduler” functionality provided by
Spring Framework; its usage is not mandatory. In an actual project, the infrastructure team may provide
batch application (Shell application) to delete unnecessary files. In such cases, it is recommended to delete
unnecessary files using the batch application created by infrastructure team.
package com.examples.common.upload;
import java.io.File;
import java.util.Collection;
import java.util.Date;
import javax.inject.Inject;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.springframework.beans.factory.annotation.Value;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
// (1)
public class UnnecessaryFilesCleaner {
@Inject
JodaTimeDateFactory dateFactory;
@Value("${app.upload.temporaryFileSavedPeriodMinutes}")
private int savedPeriodMinutes;
@Value("${app.upload.temporaryDirectory}")
private File targetDirectory;
// (2)
public void cleanup() {
if (targetFiles.isEmpty()) {
return;
}
// delete files.
for (File targetFile : targetFiles) {
FileUtils.deleteQuietly(targetFile);
}
Note: Directory path in which files to be deleted are stored or the time criteria for deletion etc. may differ
depending upon the environment in which application is to be deployed. Hence they should be fetched
from external properties. For details on external properties, refer to Properties Management.
Carry out bean registration and task schedule settings for the POJO class that deletes unnecessary files.
• applicationContext.xml
(3) POJO class that deletes unnecessary files should be registered in bean.
In the above example, it is registered with "uploadTemporaryFileCleaner" ID.
(4) Register the bean for task scheduler that executes the process to delete unnecessary files.
In the above example, as pool-size attribute is omitted, this task scheduler executes the task in a
single thread .
When multiple tasks need to be executed simultaneously, some number should be specified in
pool-size attribute.
(5) Add the task to the task scheduler that deletes unnecessary files.
In the above example, task is added to the task scheduler for which bean is registered in step (4).
(6) In ref attribute, specify the bean that executes the process of deleting unnecessary files.
In the above example, the bean registered in step (3) is specified.
(7) In method attribute, specify the name of method executing the process of deleting unnecessary
files.
In the above example, cleanup method of bean registered in step (3) is specified.
(8) In cron attribute, specify execution time of the process to delete unnecessary files.
In the above example, cron definition is fetched from external properties.
Note: Specify the configuration value of cron attribute in “seconds minutes hour month year day” format.
Example:
Execution time should be fetched from external properties as it may differ depending on the environment in
which the application is to be deployed. For details on external properties, refer to Properties Management.
Tip: In the above example, cron is used as a trigger for executing tasks. However, other triggers namely
fixed-delay and fixed-rate are also set by default and should be selectively used as per requirement.
When the default triggers do not satisfy the requirements, an independent trigger can be set by specifying
the bean implementing org.springframework.scheduling.Trigger in trigger attribute.
4.9.4 Appendix
Following security issues need to be considered when providing File Upload functionality.
Dos attack with respect to upload functionality is when load on the server is increased by continuously uploading
large files, thereby crashing the server or reducing its response speed.
When there is no limit on the size of files to be uploaded and multipart request, the resistance to Dos attack
becomes weak.
In order to enhance the resistance towards Dos attack, size limit needs to be set for a request, by using
<multipart-config> element explained in Application settings.
In this attack, the files on Web Server can be viewed/altered/deleted by uploading and executing the script files
(php, asp, aspx, jsp etc.) that are executable on Web Server (Application Server).
With Web Server as a platform, another server present in the same network as the Web server, is also vulnerable
to such attack.
• To view the file contents through a process that displays the contents, without placing uploaded files in the
public directory of Web Server (Application Server).
• To ensure that executable script file cannot be uploaded on Web server (Application Server) by restricting
the extension of files that can be uploaded.
The attacks can be prevented by implementing either of the above measures; however it is always recommended
to implement both the measures.
Directory traversal attack is an attack that accesses the files on the server that originally should not been
accessed, by accessing the file system using inputs which include strings like ”../” etc.
For example, in the web application wherein the file uploaded by the user is placed in the predetermined
directory on the server, the file with the name ”../../../../somewhere/attack” is uploaded according to
implementation method and the file is placed in a directory other than predetermined directory.
In that case, the file on the server is likely to get tampered by the file uploaded by the attacker.
A risk of directory traversal is likely in the file download function as well, along with file upload function.
For example, a type of attack can be considered wherein the attacker would obtain the details of “/etc/passwd” by
entering ”../../../../etc/passwd”, in case of Web application wherein file is downloaded in accordance with the file
name entered by the user.
• When the uploaded file is to be stored on the server, it should be stored by using a different name, and the
original file name and input value from user should not be used. It should be stored in a format which is
not used for actual file access such as storing the correspondence relation with the file name on the server
outside DB, for the original file name.
• When the file is to be accessed from the server, a request should be sent using an identifier for request
instead of using actual file name and then converted to the file name on the server side. For example,
identifiers “id01” and “id02” are used for actual file names “file_A” and “file_B” wherein if the client
requests for “id01”, corresponding “file_A” on the server is accessed.
Tip: Another countermeasure can be taken into consideration wherein file path thus entered is normalised ( ”./”
or ”../” etc, should be developed in a format which does not include strings with specific significance in the file
system) and the access is given after checking whether the path matches (begins-with match) with the path already
determined. However, if input value encoding and the variation in the path format according to OS is taken into
consideration, it is difficult to determine whether the normalization has been done appropriately. Hence, it is
preferable to avoid accessing the file system by using the input value from user.
If File Upload functionality of Servlet 3.0 is only used partially on Application Server, it may likely result into
garbling of multi byte characters of file names or request parameters.
For example: If File Upload functionality of Servlet 3.0 is used on WebLogic 12.1.3, it has been confirmed that
multi byte characters of fields to be sent along with file are garbled. Note that it has been corrected in WebLogic
12.2.1.
This problem can be avoided using Commons FileUpload. Therefore, this guideline describes about file
upload using Commons FileUpload as a temporary measure for the specific environment where problems
are likely to occur. Using Commons FileUpload is not recommended where problems are not likely.
xxx-web/pom.xml
Warning: In case of using Apache Commons FileUpload, security vulnerabilities reported in CVE-2014-
0050 and CVE-2016-3092 are likely to occur. Confirm that there are no vulnerabilities in the version of
Apache Commons FileUpload to be used.
When using Apache Commons FileUpload, version 1.3.2 or above should be used.
Note that, if a version managed by Spring IO Platform 2.0.6.RELEASE which is in conformance with TERA-
SOLUNA Server Framework for Java version 5.2.0.RELEASE is used, vulnerabilities reported in CVE-2014-
0050 and CVE-2016-3092 do not occur. When Apache Commons FileUpload version is to be changed inten-
tionally, a version wherein corresponding vulnerability has been addressed must be specified.
xxx-web/src/main/resources/META-INF/spring/applicationContext.xml
xxx-web/src/main/webapp/WEB-INF/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-ap
version="3.0">
<servlet>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- omitted -->
<!-- (1) -->
<!-- <multipart-config>...</multipart-config> -->
</servlet>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Sr.No. Description
(1) When using Commons FileUpload, an upload function of Servlet 3.0 should be disabled.
If <multipart-config> element is present in DispatcherServlet definition, make sure to
delete the same.
(2) When using Commons Fileupload, MultipartFilter must be defined to enable security
countermeasures which use Spring Security.
MultipartFiltermapping should be defined before defining springSecurityFilterChain (Servlet
Filter of Spring Security).
4.10.1 Overview
This chapter explains the functionality to download a file from server to client, using Spring.
It is recommended to use Spring MVC View for rendering the files.
This is because, it deviates from the role of a controller. Moreover, a View can be easily changed when it
is isolated from the controller.
For file formats other than those specified above, various types of View implementations are provided in Spring.
For technical details on View, refer to Spring Reference View technologies.
Tip: A countermeasure for directory traversal attack may become necessary while offering a file download
function. For directory traversal attack, refer Directory traversal attack .
@Component // (1)
public class SamplePdfView extends AbstractPdfView { // (2)
@Override
protected void buildPdfDocument(Map<String, Object> model,
Document document, PdfWriter writer, HttpServletRequest request,
HttpServletResponse response) throws Exception { // (3)
(1) In this example, this class comes under the scope of component scanning by using @Component
annotation.
It will also come under the scope of
org.springframework.web.servlet.view.BeanNameViewResolver which is
described later.
<dependencies>
<!-- omitted -->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<exclusions>
<exclusion>
<artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId>
</exclusion>
<exclusion>
<artifactId>bctsp-jdk14</artifactId>
<groupId>org.bouncycastle</groupId>
</exclusion>
<exclusion>
<artifactId>jfreechart</artifactId>
<groupId>jfree</groupId>
</exclusion>
<exclusion>
<artifactId>dom4j</artifactId>
<groupId>dom4j</groupId>
</exclusion>
<exclusion>
<groupId>org.swinglabs</groupId>
<artifactId>pdf-renderer</artifactId>
</exclusion>
<exclusion>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk14</artifactId>
<version>1.38</version>
</dependency>
</dependencies>
Definition of ViewResolver
Note: Spring Framework provides various types of ViewResolver and it allows chaining of multiple
ViewResolver. Therefore, some unintended View may get selected under certain conditions.
It is possible to avoid such a situation by setting appropriate priority order in ViewResolver. Method to set
priority order differs depending on definition method of ViewResolver.
• When defining ViewResolver using <mvc:view-resolvers> element added from Spring Frame-
work 4.1, definition order of ViewResolver specified in child element will be the priority order. (exe-
cuted sequentially from top)
<mvc:view-resolvers>
<mvc:bean-name /> <!-- (1) (2) -->
<mvc:jsp prefix="/WEB-INF/views/" />
</mvc:view-resolvers>
(2) Define <mvc:bean-name>element right at the top so that it has a higher priority than the generally
used ViewResolver(ViewResolverfor JSP).
Tip: <mvc:view-resolvers> element is an XML element added from Spring Framework 4.1. If
<mvc:view-resolvers> element is used, it is possible to define ViewResolver in a simple way.
Example of definition when <bean> element used in a conventional way is given below.
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver">
<property name="order" value="0"/>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="1" />
</bean>
a view named “samplePDFView” gets used from the BeanIDs stored in Spring Context.
Following PDF file can be opened after executing the above procedure.
@Component // (1)
public class SampleExcelView extends AbstractXlsxView { // (2)
@Override
protected void buildExcelDocument(Map<String, Object> model,
Workbook workbook, HttpServletRequest request,
HttpServletResponse response) throws Exception { // (3)
Sheet sheet;
Cell cell;
sheet = workbook.createSheet("Spring");
sheet.setDefaultColumnWidth(12);
// write a text at A1
cell = getCell(sheet, 0, 0);
setText(cell, "Spring-Excel test");
(1) In this example, this class comes under the scope of component scanning by using @Component
annotation.
It will also come under the scope of
org.springframework.web.servlet.view.BeanNameViewResolver which is
described earlier.
<dependencies>
<!-- omitted -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
</dependency>
<exclusions>
<exclusion>
<groupId>stax</groupId>
<artifactId>stax-api</artifactId>
</exclusion>
</exclusions>
</dependencies>
Note: Since stax-api on which poi-ooxml is dependent, is provided as a standard from SE, the library is
not required. Also, since a conflict is likely in the library, <exclusions> element should be added and
the relevant library should not be added in the application.
Note: <version> is omitted in the configuration example since poi-ooxml version uses details defined in
Spring IO Platform.
Also, AbstractExcelView uses @Deprecated annotation from Spring Framework 4.2. Hence, it is
recommended to use AbstractXlsxViewin the same way even if you want to use a xls file. For details,
refer AbstractExcelView - JavaDoc.
Definition of ViewResolver
Settings are same as that for PDF file rendering. For details, refer to Definition of ViewResolver.
Java source
EXCEL file can be opened as shown below after executing the above procedures.
@Component // (1)
public class TextFileDownloadView extends AbstractFileDownloadView { // (2)
@Override
protected InputStream getInputStream(Map<String, Object> model,
HttpServletRequest request) throws IOException { // (3)
Resource resource = new ClassPathResource("abc.txt");
return resource.getInputStream();
}
@Override
protected void addResponseHeader(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response) { // (4)
response.setHeader("Content-Disposition",
"attachment; filename=abc.txt");
response.setContentType("text/plain");
}
}
(1) In this example, this class comes under the scope of component scanning by using @Component
annotation.
It will also come under the scope of
org.springframework.web.servlet.view.BeanNameViewResolver which is
described earlier.
Definition of ViewResolver
Settings are same as that of PDF file rendering. For details, refer to Definition of ViewResolver.
Java source
Tip: As described above, Model information can be rendered in various types of Views using Spring.
Spring supports rendering engine such as Jasper Reports and returns various types of views. For details,
refer to the official Spring website Spring reference.
4.11.1 Overview
When developing a Web application with common layouts such as header, footer and side menu, maintaining the
layouts becomes complicated if the common parts are coded in all JSPs.
For example, if the header design needs to be modified, the same modifications must be done for all JSPs.
In JSP development, when the same layout is used in many screens, it is recommended to use Apache Tiles
(hereafter referred to as Tiles).
Reasons for using Tiles are as follows:
By using Tiles, one can focus only on creating the body without having to include and specify the sizes of header,
menu and footer, in all the screens with the same layout.
Actual JSP file is as follows:
Therefore, after configuring the screen layout using Tiles, only the JSP file corresponding to business process
(business.jsp) may be created for each screen.
Note: In some cases, it is better to avoid using Tiles. For example, using Tiles in an error screen is not
recommended due to the following reasons.
• If an error occurs due to Tiles during error screen display, analyzing the errors becomes difficult. (In
case of double failure)
• Tiles Template is not necessarily always used to display screens in the JSP set by the <error-pages>
tag of web.xml.
pom.xml setting
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-web-dependencies</artifactId><!-- (1) -->
<type>pom</type><!-- (2) -->
</dependency>
(1) Add terasoluna-gfw-recommended-web-dependencies defined for the group of web related libraries,
to dependency.
<parent>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-parent</artifactId>
<version>x.y.z</version>
</parent>
• spring-mvc.xml
<mvc:view-resolvers>
<mvc:tiles /> <!-- (1) -->
<mvc:jsp prefix="/WEB-INF/views/" /> <!-- (2) -->
</mvc:view-resolvers>
Tip: <mvc:view-resolvers> element is an XML element added from Spring Framework 4.1. If
Example of definition when <bean> element is used in a conventional way is given below.
<bean id="tilesViewResolver"
class="org.springframework.web.servlet.view.tiles3.TilesViewResolver">
<property name="order" value="1" />
</bean>
<bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles/tiles-definitions.xml</value>
</list>
</property>
</bean>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="2" />
</bean>
Tiles Definition
• tiles-definitions.xml
<tiles-definitions>
<definition name="layouts"
template="/WEB-INF/views/layout/template.jsp"> <!-- (2) -->
<put-attribute name="header"
value="/WEB-INF/views/layout/header.jsp" /> <!-- (3) -->
<put-attribute name="footer"
value="/WEB-INF/views/layout/footer.jsp" /> <!-- (4) -->
</definition>
</tiles-definitions>
(5) Layout definition which is called when it is same as name pattern at the time of ‘create’ request.
Extended layouts definition is also applied.
(7) Design the location of jsp file that defines the body such that, request path matches with {1}
and JSP name matches with {2}.
With this, the efforts to describe definition for each request can be saved.
Note: For the screens where Tiles is not to be applied (error screen etc.), it is necessary to set a file structure
that excludes use of Tiles. In Blank project, /WEB-INF/views/common/error/xxxError.jsp format is used
so that InternalResourceViewResolver can be used (and so that it does not change to the “*/*” format) on
error screen.
• application-messages.properties
• /WEB-INF/views/common/include.jsp
Tip:
Tiles version-2 had one taglib, but tiles-extras taglib is added from version-3.
useAttribute tag which was available in tiles taglib in version-2 is moved to tiles-extras taglib from version-3,
hence should be careful while using.
e.g. ) <tiles:useAttribute> : version 2 -> <tilesx:useAttribute> : version 3
• web.xml
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<include-prelude>/WEB-INF/views/common/include.jsp</include-prelude> <!-- (1) -->
</jsp-property-group>
</jsp-config>
(1) Based on web.xml settings, when jsp file (~.jsp) is to be read, include.jsp can be read in
advance.
Note: Custom tag can also be set in template.jsp. However, it is recommended to create custom tag
definition in common jsp include file. For details, refer to Creating common JSP for include.
Creating layout
Create jsp (template) that forms frame of a layout and jsp to be embedded in the layout.
• template.jsp
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css"
type="text/css" media="screen, projection">
<script type="text/javascript">
</html>
(1) Mention the common contents that need to be described, above step (1).
(2) Fetch the value of title specified in step (6) of tiles-definitions.xml and set it to titleKey.
• header.jsp
<h1>
<a href="${pageContext.request.contextPath}">Staff Management
System</a>
</h1>
The developer is able to focus only on the body section and describe the same without having to
mention the extra source code for header and footer.
</tr>
<tr>
<td rowspan="5">Staff Authorities</td>
<td><input type="checkbox" name="sa" value="01" /> Staff
Management</td>
</tr>
<tr>
<td><input type="checkbox" name="sa" value="02" /> Master
Management</td>
</tr>
<tr>
<td><input type="checkbox" name="sa" value="03" /> Stock
Management</td>
</tr>
<tr>
<td><input type="checkbox" name="sa" value="04" /> Order
Management</td>
</tr>
<tr>
<td><input type="checkbox" name="sa" value="05" /> Show Shopping
Management</td>
</tr>
</table>
• footer.jsp
Creating Controller
While creating Controller, when the request is <contextPath>/staff/create?form, perform the settings
such that “staff/createForm” is returned from the Controller.
• StaffCreateController.java
(1) With staff as {1} and createForm as {2}, fetch the title name from properties and identify the
JSP.
Creating screen
When <contextPath>/staff/create?form is called in request, Tiles construct the layout and create
screen, as shown below.
<definition name="layouts"
template="/WEB-INF/views/layout/template.jsp"> <!-- (1) -->
<put-attribute name="header"
value="/WEB-INF/views/layout/header.jsp" /> <!-- (2) -->
<put-attribute name="footer"
value="/WEB-INF/views/layout/footer.jsp" /> <!-- (3) -->
</definition>
(1) In case of corresponding request, “layouts” which is a parent layout is called and template is set
to /WEB-INF/views/layout/template.jsp.
(4) With title.staff.createForm as key, fetch the value from properties incorporated in
spring-mvc where staff is {1} and createForm is {2}.
As a result, it is output to the browser by combining header.jsp, createForm.jsp and footer.jsp in the above tem-
plate.jsp.
When creating actual business application, display layout may be divided depending on business process
contents.
This time, it is assumed that the staff search functionality menu is required to be displayed on left side of the
screen.
Configuration is shown below based on How to use.
Tiles Definition
• tiles-definitions.xml
<tiles-definitions>
<definition name="layoutsOfSearch"
template="/WEB-INF/views/layout/templateSearch.jsp"> <!-- (1) -->
<put-attribute name="header"
value="/WEB-INF/views/layout/header.jsp" />
<put-attribute name="menu"
value="/WEB-INF/views/layout/menu.jsp" />
<put-attribute name="footer"
value="/WEB-INF/views/layout/footer.jsp" />
</definition>
<definition name="layouts"
template="/WEB-INF/views/layout/template.jsp">
<put-attribute name="header"
value="/WEB-INF/views/layout/header.jsp" />
<put-attribute name="footer"
value="/WEB-INF/views/layout/footer.jsp" />
</definition>
(2) Layout definition called when the layout to be added is same as the name pattern at the time of
‘create’ request.
This layout definition is read when the request corresponds to <contextPath>/*/search*.
Extended layout definition “layoutsOfSearch” is also applied.
(4) Place the jsp file in which the body is defined such that, the request path matches with {1} and
JSP file name beginning with “search”, matches with {2}.
The value of ‘value’ attribute needs to be changed according to the configuration of JSP file
location.
Note: When multiple requests correspond to name attribute patterns of definition tag, the verification is
done sequentially from the top and the very first pattern that matches with the request is applied. In the
above case, as the request for staff search screen corresponds to multiple patterns, the layout is defined at
the top.
• application-messages.properties
Creating layout
Create the jsp (template) that forms the frame of the layout and jsp to be embedded in layout.
• templateSearch.jsp
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js">
<!--<![endif]-->
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width" />
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css"
type="text/css" media="screen, projection">
<script type="text/javascript">
</script>
<c:set var="titleKey">
<tiles:insertAttribute name="title" ignore="true" />
</c:set>
<title><spring:message code="${titleKey}" text="Search Staff Information" /></title>
</head>
<body>
<div id="header">
<tiles:insertAttribute name="header" />
</div>
<div id="menu">
<tiles:insertAttribute name="menu" /> <!-- (1) -->
</div>
<div id="body">
<tiles:insertAttribute name="body" />
</div>
<div id="footer">
<tiles:insertAttribute name="footer" />
</div>
</body>
</html>
• styles.css
div#menu { /* (1) */
float: left;
width: 20%;
}
div#searchBody { /* (2) */
float: right;
width: 80%;
}
div#footer { /* (3) */
clear: both;
}
• header.jsp
• menu.jsp
<table>
<tr>
<td><a href="${pageContext.request.contextPath}/staff/create?form">Create Staff Infor
</tr>
<tr>
<td><a href="${pageContext.request.contextPath}/staff/search">Search Staff Informatio
</tr>
</table>
• footer.jsp
Creating Controller
While creating Controller, if the request is <contextPath>/staff/search, set such that “staff/searchStaff”
is returned from the Controller.
• StaffSearchController.java
(1) With staff as {1} and searchStaff as {2}, fetch the title name from properties and identify the
JSP.
Creating screen
<definition name="layoutsOfSearch"
template="/WEB-INF/views/layout/templateSearch.jsp"> <!-- (1) -->
<put-attribute name="header"
value="/WEB-INF/views/layout/header.jsp" /> <!-- (2) -->
<put-attribute name="menu"
value="/WEB-INF/views/layout/menu.jsp" /> <!-- (3) -->
<put-attribute name="footer"
value="/WEB-INF/views/layout/footer.jsp" /> <!-- (4) -->
</definition>
(1) In case of a corresponding request, “layoutsOfSearch” which is a parent layout is called and
template is set in /WEB-INF/views/layout/templateSearch.jsp.
(5) This layout definition is read when the request corresponds to <contextPath>/*/search*.
In that case, “layoutsOfSearch” which is a parent layout is also read.
(6) With title.staff.searchStaff as key, fetch the value from properties incorporated in
spring-mvc, where staff is {1} and searchStaff is “search{2}”.
As a result, it is output to the browser by combining header.jsp, menu.jsp, searchStaff.jsp and footer.jsp in the
above templateSearch.jsp file.
4.12.1 Overview
Below are the JSP Tag Libraries and EL Functions offered by common library as an ability to support the JSP
implementation.
EL Functions
URL related
DOM related
Utility
4.12. JSP Tag Libraries and EL Functions offered by common library 793
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
The use of JSP Tag Library and EL function offered by common library explained below. The appropriate Hyper-
link is placed at appropriate location if detail description explained in other chapters.
<t:pagination>
The <t:pagination> tag is a JSP Tag Library to output the pagination link by referring the information stored
in page search results (org.springframework.data.domain.Page).
For detail description of pagination and how to use this tag, Refer the following section [Pagination].
• For basic implementation of the JSP using this tag, [Display of Pagination link]
• For the layout of how to change the pagination link, [Implementation of JSP (layout change)]
<t:messagesPanel>
The <t:messagesPanel> tag is a JSP Tag Library to output the processing result message, (Such as
org.terasoluna.gfw.common.message.ResultMessage or message having exception).
Refer the following section [Message Management] for how to use this tag.
• For how to display messages using this tag, [Display of result messages]
<t:transaction>
The <t:transaction> tag is a JSP Tag Library to output the transaction token as hidden item (<input
type="hidden">").
Refer the following section [Double Submit Protection] for the transaction token check feature and how to use this
tag.
• For how to use this tag, [How to use transaction token check in View (JSP)]
Note: This tag is used for sending a transaction token to the server while using standard HTML <form> tag.
No need to use this tag if spring framework offered <form:form> tag (JSP Tag Library) has been used because
org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor
offered by the common library has been already mechanized to handle a transaction token.
f:h()
The f:h() is an EL Function which converts the specified object into a string and escape the HTML special
characters from converted string.
Refer [Output Escaping] for the specification of HTML special characters and escaping.
Argument
4.12. JSP Tag Libraries and EL Functions offered by common library 795
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Return value
For the information about how to use f:h() function, refer [Example of escaping output value using f:h() func-
tion].
f:js()
The f:js() is an EL Function which escape the JavaScript special characters from the specified string argument.
Refer [JavaScript Escaping] for the specification of JavaScript special characters and escaping.
Argument
Return value
For the information about how to use f:js() function, refer [Example of escaping output value using f:js()
function].
f:hjs()
The f:hjs() is an EL Function which escapes the HTML special characters after escaping the JavaScript special
characters from the specified string argument, (short function of f:h(f:js())).
• Refer [JavaScript Escaping] for the specification of JavaScript special characters and escaping.
• Refer [Output Escaping] for the specification of HTML special characters and escaping.
Argument
Return value
For the information about how to use f:hjs() function, refer [Example of escaping output value using f:hjs()
function].
f:query()
The f:query() is an EL Function which generates the query string from java.util.Map object or JavaBean
(form object) that is specified in the argument. Parameter names and parameter values in the query string are URL
encoded in UTF-8.
In this function, the parameter name and parameter value of the query string are encoded on RFC 3986 basis. In
RFC 3986, the part of query string is defined as follows.
4.12. JSP Tag Libraries and EL Functions offered by common library 797
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• sub-delims = "!" / "$" / "&" / "’" / "(" / ")" / "*" / "+" / "," / ";" / "="
In this function, one of the character that can be used as a query string,
• "+" (Character that represent a space when you submit HTML Form)
Argument
Note: A simple type property value of the specified object is converted into string using the convert method
of org.springframework.format.support.DefaultFormattingConversionService.
Return value
f:query() converts an object so that the Spring Web MVC can handle it at binding process provided.
Case that property type is an instance of Map Property name + [Map’s status[accepting]=Accept
key name] Order
Case that property type(including element Value that combined a prop-
type in Iterable,Array and Map) is Jav- erty name with "."(dot)
aBean mainContract.name=xxx
subContracts[0].name=xxx
_status[accepting]=
4.12. JSP Tag Libraries and EL Functions offered by common library 799
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
For the information about how to use f:query() function, refer [Carrying forward search conditions using
page link]. Here, this function is used as to carry forward the search criteria while switching the pages using the
pagination link. Further, function description and the specification also described here and that should be read.
f:u()
The f:u() is an EL Function which performs UTF-8 URL encoding on specified string argument.
This function is provided for performing URL encoding on those values which are going to be set as parameter
values in the query string. For the URL encoding specification, refer [f:query()]
Argument
Return value
<div id="url">
<a href="https://search.yahoo.com/search?p=${f:u(bean.searchString)}"> <!-- (1) -->
Go to Yahoo Search
</a>
</div>
Sr.No Description
In the above example, sets the URL-encoded value to the request parameters of the search site using
this function.
(1)
f:link()
The f:link() is an EL Function which generates a hyperlink (<a> tag) for jumping to specified URL which is
specified in the argument.
Warning: Please note that, this function is not going to escaping the special characters nor performing the
URL encoding.
Argument
Return value
4.12. JSP Tag Libraries and EL Functions offered by common library 801
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Implementation
<div id="link">
${f:link(bean.httpUrl)} <!-- (1) -->
</div>
Output
<div id="link">
<a href="http://terasoluna.org/">http://terasoluna.org/</a> <!-- (2) -->
</div>
Sr.No Description
(1) Generated Hyper link from the URL string specified in the argument.
(2) URL string specified in the argument set in the href attribute of <a> tag and link name of the hyper
link.
Warning: When adding the request parameters to the URL, the value of the request parameters should be
URL encoded. When adding the request parameters, the value of the request parameters should be URL
encoded using appropriate f:query() function or f:u() function.
In addition, it has been described in the return value description, if the format of the URL string specified in
the argument is not appropriate, it returns the input string value without generating a hyperlink. Therefore, if
you want to use the input value from the user as a URL string in the argument, similar to string output process,
the escaping process of the HTML special characters (XSS Countermeasures) are required.
f:br()
The f:br() is an EL Function which converts the new line character (CRLF, LF, CR) specified in the argument
into <br /> tag.
Tip: If you want to display a string containing new line code as a newline on browser, it is necessary to convert
the new line code into ‘‘ <br /> ‘‘ tag.
For example, if you want to display the string entered in the textarea (<textarea>) of the input screen as it is
on the confirmation screen or completion screen, it is advisable to use this function.
Argument
Return value
<div id="text">
${f:br(f:h(bean.text))}"> <!-- (1) -->
</div>
Sr.No Description
The newline displays on the browser by converting the new line character into <br /> tag from the
specified string argument.
(1)
Note: When you display a string on the screen, there is a need to escape the HTML special character as [XSS
Countermeasures].
if you are converting new line code into <br /> tag using f:br() function, as in the above example, a string
that has escaped the HTML special characters need to pass as an argument to f:br() function.
The string obtained by converting new line code into <br /> tag using f:br() function passes as an argument
4.12. JSP Tag Libraries and EL Functions offered by common library 803
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
to the f:h() function, the letter "<br />" get displayed on the browser hence be careful in order to call the
function.
f:cut()
The f:cut() is an EL Function which extracts specified number of characters from the specified string.
Argument
Return value
<div id="cut">
${f:h(f:cut(bean.originText, 5))} <!-- (1) -->
</div>
Sr.No Description
(1) In the above example, you can extract the first five characters of the string that was specified in the
argument and displays on the screen.
Note: There is a need to escape the HTML special character as [XSS Countermeasures] while displaying the
extracted string on the screen. In the above example, string is escaped by using f:h() function.
4.12. JSP Tag Libraries and EL Functions offered by common library 805
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
4.13 Ajax
4.13.1 Overview
Todo
TBD
Details regarding client side implementation etc. will follow in subsequent versions.
Ajax is the generic term used for group of techniques that perform the following asynchronous processes.
• HTTP communication with the server triggered by a screen operation and reflecting back the communica-
tion results to the user interface
Ajax is often used to improve usability. This is because, screen operations can be continued during HTTP
communication.
Typical examples of Ajax are (a) Providing suggestions while searching words and (b) Real time search of a
search site.
Application settings
Warning: DoS attack measures at the time of StAX(Streaming API for XML) use
If the StAX is used to parse the XML format data, protect DoS attack. For details, refer to CVE-2015-3192 -
DoS Attack with XML Input.
• spring-mvc.xml
Note: Functionalities required for Ajax communication specifically refer to the ones provided by
org.springframework.http.converter.HttpMessageConverter class.
• Creating the data to be written to the response Body from Java object.
1.
JSON HttpMessageConverter to handle JSON
org.springframework.http.converter.json.
as request body or response body.
Jackson system is included in the blank project.
MappingJackson2HttpMessageConverter
Hence, it can be used in its default state.
2.
XML HttpMessageConverter to handle XML
org.springframework.http.converter.xml.
as request body or response body.
JAXB2.0 is included as standard from JavaSE6.
Jaxb2RootElementHttpMessageConverter
Hence it can be used in its default state.
Note: Notice If you change from jackson version 1.xx to jackson version 2.xx, refer to here.
<mvc:annotation-driven>
<mvc:message-converters>
<!-- (3) -->
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConv
<property name="marshaller" ref="xmlMarshaller" /> <!-- (4) -->
<property name="unmarshaller" ref="xmlMarshaller" /> <!-- (5) -->
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
(2) Specify the package name where the JAXB JavaBean (JavaBean assigned with
javax.xml.bind.annotation.XmlRootElement annotation ) is stored in
the packagesToScan property.
JAXB JavaBean stored under the specified package is scanned and registered for
marshalling or unmarshalling the JavaBean.
It is scanned in the same way as the base-package attribute of
4.13. Ajax 809
<context:component-scan>.
Implementing Controller
• JQuery should be used at client side. It should be the latest version of 1.x series (1.10.2), which is used
while writing this document.
(1)
Fetching data
Following example serves as the Ajax communication that returns a list matching with the search word.
// (1)
public class SearchCriteria implements Serializable {
// omitted
// omitted setter/getter
// (3)
public class SearchResult implements Serializable {
// omitted
// omitted setter/getter
• Controller
// (8)
// omitted
(8) Search data and store the search result in the object created in (7).
In the above example, implementation is omitted.
• HTML(JSP)
Note: Refer to the settings below to read JQuery JavaScript file. Setting values provided in the blank
project are as follows.
• spring-mvc.xml
(12) Settings for releasing resource files (JavaScript files, Stylesheet files, image files etc.).
In the above setting example, when there is a request for path starting with
/resources/, the files in /resources/ directory of war file or the
/META-INF/resources/ directory of class path are sent as a response.
In the above settings, the JQuery JavaScript file needs to be placed under any one of the following paths.
• JavaScript
// (13)
function searchByFreeWord() {
$.ajax(contextPath + "/ajax/search", {
type : "GET",
data : $("#searchForm").serialize(),
dataType : "json", // (14)
}).done(function(json) {
// (15)
// render search result
// omitted
}).fail(function(xhr) {
// (16)
// render error message
// omitted
});
return false;
}
(13) Ajax function that converts search criteria specified in the form to request parameter and sends
the request for /ajax/search using GET method.
In the above example, clicking the button acts as the trigger for Ajax communication. However,
by setting key down or key up of text box as the trigger, real time search can be performed.
(15) Implement the process when Ajax communication ends normally (when Http status code is
"200").
In the above example, implementation is omitted.
(16) Implement the process when Ajax communication does not end normally (when Http status
code is "4xx" and "5xx").
In the above example, implementation is omitted.
For error process implementation, refer to Posting form data.
• Request data
• Response data
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: a8fb8fefaaf64ee2bffc2b0f77050226
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 25 Oct 2013 13:52:55 GMT
{"list":[]}
How to post form data and fetch processing result using Ajax, is explained here.
Following example is about the Ajax communication of receiving two numbers and returning the calculation
result.
// (1)
public class CalculationParameters implements Serializable {
// omitted
// omitted setter/getter
// (2)
public class CalculationResult implements Serializable {
// omitted
// omitted setter/getter
• Controller
@RequestMapping("xxx")
@Controller
public class XxxController {
// omitted
(5) Store the processing result in the object created for the same.
In the above example, calculation result of the two numbers fetched from form object, is stored.
• HTML (JSP)
<sec:csrfMetaTags />
• JavaScript
// (9)
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeaderName = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(event, xhr, options) {
xhr.setRequestHeader(csrfHeaderName, csrfToken);
});
// (10)
function plus() {
$.ajax(contextPath + "/ajax/plusForForm", {
type : "POST",
data : $("#calculationForm").serialize(),
dataType : "json"
}).done(function(json) {
$("#calculationResult").text(json.resultNumber);
}).fail(function(xhr) {
// (11)
var messages = "";
// (12)
if(400 <= xhr.status && xhr.status <= 499){
// (13)
var contentType = xhr.getResponseHeader('Content-Type');
if (contentType != null && contentType.indexOf("json") != -1) {
// (14)
json = $.parseJSON(xhr.responseText);
$(json.errorResults).each(function(i, errorResult) {
messages += ("<div>" + errorResult.message + "</div>");
});
} else {
// (15)
messages = ("<div>" + xhr.statusText + "</div>");
}
}else{
// (16)
messages = ("<div>" + "System error occurred." + "</div>");
}
// (17)
$("#calculationResult").html(messages);
});
return false;
}
(9) To send the request using POST method, CSRF token needs to be set to HTTP header.
In the above example, the header name and token value are set in the <meta> element of
HTML and value is fetched by JavaScript.
For details on CSRF measures, refer to CSRF Countermeasures.
(10) Ajax function that converts the numerical value specified in form, to request parameter and
sends the request for /ajax/plusForForm using POST method.
In the above example, clicking the button acts as the trigger for Ajax communication however,
real time calculation can be implemented by setting lost focus of the text box as the trigger.
(15) Perform the process when the response data is not in JSON format.
822 4 Web Application Development Features
In the above example, HTTP status text is stored in the error message.
HTTP status text is stored in the statusText field of XMLHttpRequest object.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: In the above example, processes namely, Ajax communication, DOM operation (rendering)
and error handling are performed by the same function. It is recommended to split and implement these
processes.
Todo
TBD
Tip: In the above example, JSP code is deleted from JavaScript code by setting CSRF token value and
CSRF token header name, in the <meta> element of HTML using <sec:csrfMetaTags />. Please
refer, Coordination while using Ajax.
Please note that, CSRF token value and name of CSRF token header can also be fetched by using
${_csrf.token} and ${_csrf.headerName} respectively.
Following communication occurs when the “=” button of search form is clicked.
Main points are highlighted.
• Request data
number1=1&number2=2
• Response data
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: c2d5066d0fa946f584536775f07d1900
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Fri, 25 Oct 2013 14:27:55 GMT
{"resultNumber":3}
{"errorResults":[{"code":"NotNull","message":"\"number2\"maynotbenull.","itemPath":"number2"}
How to fetch processing result by converting form data to JSON format and subsequently posting it using Ajax, is
explained here.
Difference between this method and “Posting form data” method, is explained.
• Controller
@RequestMapping("xxx")
@Controller
public class XxxController {
// omitted
• JavaScript/HTML (JSP)
// (2)
function toJson($form) {
var data = {};
$($form.serializeArray()).each(function(i, v) {
data[v.name] = v.value;
});
return JSON.stringify(data);
}
function plus() {
$.ajax(contextPath + "/ajax/plusForJson", {
type : "POST",
contentType : "application/json;charset=utf-8", // (3)
data : toJson($("#calculationForm")), // (2)
dataType : "json",
beforeSend : function(xhr) {
xhr.setRequestHeader(csrfHeaderName, csrfToken);
}
}).done(function(json) {
$("#calculationResult").text(json.resultNumber);
}).fail(function(xhr) {
$("#calculationResult").text("");
});
return false;
}
(3) Change the media type of Content-Type to "application/json" as the data stored in
request body is in JSON format.
Following communication occurs when “=” button of the search form mentioned above, is clicked.
Main points are highlighted.
• Request data
Accept-Language: en-US,en;q=0.8,ja;q=0.6
Cookie: JSESSIONID=CECD7A6CB0431266B8D1173CCFA66B95
{"number1":"34","number2":"56"}
How to perform error handling when an incorrect input value is specified, is explained here.
Input error handling methods are widely classified into the following.
Handling BindException
• Controller
@RequestMapping("xxx")
@Controller
public class XxxController {
// omitted
@ExceptionHandler(BindException.class) // (1)
@ResponseStatus(value = HttpStatus.BAD_REQUEST) // (2)
@ResponseBody // (3)
public ErrorResults handleBindException(BindException e, Locale locale) { // (4)
// (5)
ErrorResults errorResults = new ErrorResults();
// omitted
(3) Assign @ResponseBody annotation to write the returned object in response body.
(4) Declare the exception class to be handled as an argument of the error handling method.
Tip: Locale object can be received as an argument while creating a message for error handling by consid-
ering internationalization.
// (6)
public class ErrorResult implements Serializable {
// (7)
public class ErrorResults implements Serializable {
(7) JavaBean to store multiple JavaBeans, each of which stores one record of error information.
JavaBeans mentioned in (6) are stored as a list.
Handling MethodArgumentNotValidException
• Controller
@ExceptionHandler(MethodArgumentNotValidException.class) // (1)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResults handleMethodArgumentNotValidException(
MethodArgumentNotValidException e, Locale locale) { // (1)
ErrorResults errorResults = new ErrorResults();
return errorResults;
}
Handling HttpMessageNotReadableException
org.springframework.http.converter.HttpMessageNotReadableException is the
exception class generated when a JavaBean could not be created from the data stored in Body, while binding the
data stored in the request body to JavaBean, using @RequestBody annotation.
To receive it in formats such as "application/json" or "application/xml" etc., exception handling
of MethodArgumentNotValidException needs to be performed.
• Controller
@ExceptionHandler(HttpMessageNotReadableException.class) // (1)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResults handleHttpMessageNotReadableException(
HttpMessageNotReadableException e, Locale locale) { // (1)
ErrorResults errorResults = new ErrorResults();
return errorResults;
}
When same type of JavaBean is returned in case of normal termination and in case of input error, error handling
can be performed by receiving BindingResult as the handler method argument.
This method can be used irrespective of the request data format.
When BindingResult is not to be specified as handler method argument, it is necessary to implement error
handling by the exception handling method mentioned earlier.
• Controller
// (3)
// implement error handling.
// omitted
Note: In the above example, HTTP status code 200 (OK) is returned as response for both normal process
as well as error. When it is necessary to classify HTTP status codes as per processing results, it can be
implemented by setting org.springframework.http.ResponseEntity as the return value. As
another approach, error handling can be implemented by the exception handling method mentioned earlier,
// (1)
return ResponseEntity.badRequest().body(result);
}
// omitted
// (2)
return ResponseEntity.ok().body(result);
}
(1) Return response data and HTTP status in case of input error.
(2) Return response data and HTTP status in case of normal termination.
• Method that performs error handling by providing a business exception handling method.
• Method that catches business exception in the handler method of Controller and performs error handling.
Business exceptions are handled by providing an exception handling method same as in case of input error.
This method is recommended when it is necessary to implement the same error handling in requests for multiple
handler methods.
• Controller
@ExceptionHandler(BusinessException.class) // (1)
@ResponseStatus(value = HttpStatus.CONFLICT) // (2)
@ResponseBody
public ErrorResults handleHttpBusinessException(BusinessException e, // (1)
Locale locale) {
ErrorResults errorResults = new ErrorResults();
return errorResults;
}
Business exception is caught by enclosing the process where the business error has occurred, in try clause.
This method is implemented when error handling is different for each request.
• Controller
// omitted
// (1)
try {
// (2)
} catch (BusinessException e) {
// (3)
// implement error handling.
// omitted
return ResponseEntity.status(HttpStatus.CONFLICT).body(result);
}
// omitted
return ResponseEntity.ok().body(result);
}
(1) Enclose the method call where business exception occurs, in try clause.
(3) Perform the error handling intended for business exception error.
In the above example, although error handling is omitted, it is assumed that settings for error
message etc. are performed.
4.14.1 Overview
A load balancer (hereafter referred to as LB) is used assuming that the Web system receives requests from a large
number of users.
A LB is a device to distribute the load on the Web system by allocating requests to multiple servers and
characterized in flexibly changing process efficiency of Web system by adding and deleting servers.
Health check is a function to monitor operational status of each server allocating the requests. LB uses the health
check function wherein it allocates the requests to the servers which are running normally and does not allocate
requests to the servers which are malfunctioning. Accordingly, it is possible to continue the operation without
stopping the Web system even when a failure occurs in a specific server. (it is called as a fallback)
LB periodically sends requests to the server and monitors operational status of the server by checking status code
and response returned from the server. When an abnormality occurs in Server A of Fig., LB detects the same and
does not allocate the request to server A.
Client A which is originally connected to server A allocates the request to another server (here, server B) through
LB.
(1) Health check in PING Verify operational status at the network layer level of OSI reference model.
PING is sent for a server (OS) and determined as “server running” if
appropriate response is received from the server.
(2) Health check in Verify operational status in transport layer level of OSI reference model. A
TCP/UDP request is sent to TCP port (or UDP port) of Web/AP server and determined as
“server running” if the appropriate response is received from the server.
(3) Health check in Verify operational status at the application layer level of OSI reference model.
application A HTTP request is sent to application running on Web/AP server and
determined as “running” if the appropriate response is received from the server.
Verification is not possible for operational status of the application, in health check in PING or TCP/UDP. In case
of Web application, the application must also be “running” along with running of server (OS) or Web/AP server.
Hence, this guideline recommends a health check to be performed for the application.
This guideline introduces an implementation example for the application wherein a health check is performed in
the application.
Basically, a handler consisting of the structure shown in Fig. is implemented which receives a request from LB.
(1) Run Controller, Service and Repository by receiving a request from LB.
A method to perform a simple health check also exists when only operational status is to be verified.
However, this guideline also verifies whether the structure and framework used by the application is
running correctly, using the health check and implements Controller, Service and Repository to
achieve technological configuration of targeted application as much as possible.
(2) Issue SQL from Repository and verify that the database is running.
This is because, in an application with database access, an operation cannot be performed normally if
an abnormality occurs in database, even if the application itself is running.
Status codes and responses returned by the implementation example of the guideline are as below.
An implementation example shown in Structure of health check shown in the guidelineis explained.
Repository interface
HealthCheckRepository.java
package com.example.domain.repository.healthcheck;
A requisite minimum SQL is configured since only whether database can be accessed correctly is to be verified
here.
In this guideline, SQL is configured so as to meet the following conditions.
<mapper namespace="com.example.domain.repository.healthcheck.HealthCheckRepository">
</mapper>
<mapper namespace="com.example.domain.repository.healthcheck.HealthCheckRepository">
</mapper>
Service class
HealthCheckService.java
package com.example.domain.service.healthcheck;
HealthCheckServiceImpl.java
package com.example.domain.service.healthcheck;
import healthcheck.domain.repository.healthcheck.HealthCheckRepository;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class HealthCheckServiceImpl implements HealthCheckService {
@Inject
HealthCheckRepository healthcheckRepository;
@Override
public void healthcheck() {
healthcheckRepository.healthcheck();
}
}
Controller class
HealthCheckController.java
package com.example.app.healthcheck;
import healthcheck.domain.service.healthcheck.HealthCheckService;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HealthCheckController {
@Inject
HealthCheckService healthcheckService;
(1) valueattribute acts as a URL for health check in order to verify operational status.
(2) The directory where JSP file is placed, is kept one layer deep so as not to receive configuration
of Apache Tiles. For details, refer Should not receive settings of Apache Tiles.
JSP file
Finally, a JSP file that is moved once the health check is successful, is created.
While creating JSP file, ensure that a newline character is not inserted between <%@page> directive and OK. as
shown below.
This is to minimise the data volume for the response.
ok.jsp
Other precautions must also be taken along with minimising the data volume for the response. For details, refer
Configuration for minimising the data volume for response.
A URL for health check must always be kept active by using authentication and authorization functions, during a
health check process.
For example, <sec:intercept-url> of spring-security.xml is configured for enabling the access for any
role.
An example to perform exclusion settings under /common/healthcheck is shown below.
For details, refer Authorization.
spring-security.xml
<sec:http>
<sec:intercept-url pattern="/healthcheck/**" access="permitAll"/>
<!-- omitted -->
</sec:http>
Note: If authorization control is removed, URL for health check can be accessed by anyone. Hence, countermea-
sures like using LB to prevent external access are required.
4.14.3 Appendix
As shown in JSP file, precautions must be primarily taken for following points in order to minimise data volume
for response.
As shown in Controller class, JSP file must be placed under a directory which does not conform to
<put-attribute> tag of tiles-definitions.xml so as not to receive Apache Tiles configuration.
In the default configuration of blank project, since Apache Tiles configuration is applied to JSP corresponding to
/WEB-INF/views/{1}/{2}.jsp, a directory which is a level deep in the hierarchy is created and JSP file
is placed under /WEB-INF/views/common/healthcheck/.
For details, refer Screen Layout using Tiles.
Precautions must be taken to check the impact of <jsp-config>tag of web.xml on JSP file.
When <include-prelude> and <include-coda> tags are set in <jsp-config> tag,
header and footer files are read in JSP file. Check whether the configuration includes reading of header and footer
files.
Configuration example is shown below.
web.xml
<jsp-config>
<jsp-property-group>
<url-pattern>/WEB-INF/views/common/healthcheck/ok.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
// (1)
</jsp-property-group>
</jsp-config>
(1) <include-prelude> tag and <include-coda> tag are not set in order to not to
incorporate additional headers and footers.
For example, when <%@taglib> directive is set at the beginning of JSP, in order to use tag library, additional
line breaks are output at the beginning of response.
Hence, trimDirectiveWhitespaces attribute is set in <%@page> directive and output of additional line
breaks in ok.jsp is prevented.
Warning: When WebLogic is used, line breaks are not removed when an additional character ex-
ists before <%@page>directive, even when trimDirectiveWhitespacesattribute is configured as
shown above. Hence, a different method should be used. As an example, how to configure a
<trim-directive-whitespaces> tag in <jsp-property-group> tag of web.xml is shown be-
low.
web.xml(while setting <trim-directive-whitespaces> tag)
<jsp-config>
<jsp-property-group>
<url-pattern>/WEB-INF/views/common/healthcheck/ok.jsp</url-pattern> // (1)
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<trim-directive-whitespaces>true</trim-directive-whitespaces> // (2)
</jsp-property-group>
</jsp-config>
(2) Remove additional line breaks from targeted jsp file (ok.jsp) by setting true in
<trim-directive-whitespaces> tag.
Web Services
5.1.1 Overview
This section explains the basic concept of RESTful Web Service and its development by using Spring MVC.
Refer to the following for basic description of architecture, design and implementation of RESTful Web Service
• “Architecture“
Basic architecture of RESTful Web Service is explained.
• “How to design“
Points to be considered while designing a RESTful Web Service are explained.
• “How to use“
Application structure of RESTful Web Service and API implementation methods are explained.
REST is an abbreviation of “REpresentational State Transfer”, and is one of the architecture styles
for building an application, wherein data is exchanged between client and server.
REST architecture style consists of various important fundamental rules and the services which are in accordance
with these rules (system etc.) are expressed as RESTful.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
In other words, “RESTful Web Service” is a Web service that is built in accordance with the fundamental rules of
REST.
System configuration of the application that uses RESTful Web Service mainly consists of following 2 patterns.
The basic architecture for exchanging resources between client and server is explained by using “Architecture”.
(1) A resource is directly exchanged between client application with user interface and RESTful
Web Service.
This pattern is used to separate user interface dependent logic with higher number of
requirement & specification changes and the logic for a data model which is more universal
with less number of changes.
(2) Rather than directly exchanging the resource with client applications having user interface, the
resource is exchanged between systems.
This pattern is used while building a system wherein, the business data stored by each system is
managed centrally.
RESTful Web Service is developed in TERASOLUNA Server Framework for Java (5.x) using Spring MVC func-
tionalities.
The common functionalities necessary for RESTful Web Service development are built in Spring MVC by
default.
As a result, RESTful Web Service development can be initiated without adding any specific settings or
implementations.
Main common functionalities built in Spring MVC by default, are given below.
These functionalities can only be enabled by specifying annotations in the methods of the Controller that
provides REST API.
(1) It is a function which converts the JSON or XML format message set in request BODY, to
Resource object (JavaBean) and delivers it to the Controller class method (REST API).
(2) It is a function which implements input validation for the value stored in Resource object
(JavaBean) that has been converted from message.
(3) It is a function which converts Resource object (JavaBean) returned from the Controller class
method (REST API) to JSON or XML format and sets it in response BODY.
It is necessary to implement exception handling for each project since a generic functionality for the same
is not provided by Spring MVC. For details on exception handling, refer to “Implementing exception han-
dling”.
When RESTful Web Service is developed using Spring MVC, the application is configured as given below.
Among these, implementation is necessary for the portion marked with red frame.
(1) Spring MVC Spring MVC receives a request from client and determines the REST API
(Framework) (handler method of Controller) to be called.
(2) Spring MVC converts the JSON format message specified in request
BODY to Resource object by using HttpMessageConverter.
(3) Spring MVC performs input validation for the value stored in Resource
object using Validator.
(5) REST API REST API calls Service method and performs the process for
DomainObject such as Entity etc.
(6) Service method calls the Repository method and performs CRUD process
for the DomainObject such as Entity etc.
5.1. RESTful Web Service 853
(7) Spring MVC Spring MVC converts the Resource object returned from REST API to
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
A lot of RESTful Web Service specific processing can be entrusted to Spring MVC by using the functionalities
provided by the framework.
Therefore, configuration of the module to be developed is almost same as the development of conventional Web
application that responds with HTML.
Configuration elements of the module are explained below.
(2) Resource class Java Bean representing JSON (or XML) that acts as I/O for REST API.
Annotation for Bean Validation and annotation for controlling JSON or
XML format are specified in this class.
(3) Validator Class Class that implements correlation validation for input value.
(Optional) If the correlation validation for input value is unnecessary, this class
need not be created. Hence, it is considered as optional.
For input value correlation validation, refer to “Input Validation”.
(4) Helper Class Class which implements the process that assists the process to be
(Optional) performed by the Controller.
This class is created with the aim of simplifying the Controller
processing.
Basically, it implements a method that performs conversion of
Resource object and DomainObject models.
If the model can be converted simply by using copy of the value,
“Bean Mapping (Dozer)” may be used without creating the Helper
class. Hence, it is considered as optional.
(5) The description is beyond the scope of this section since the module implemented in the
domain layer is independent of application type.
For role of each module, refer to “Application Layering” and for domain layer
development, refer to “Domain Layer Implementation”.
Sr. No Description
(6) The description is beyond the scope of this section since the module implemented in the
infrastructure layer is independent of application type.
Refer to “Application Layering” for role of each module and “Implementation of
Infrastructure Layer” for development of infrastructure layer.
Before giving a detailed explanation, an implementation sample of Resource class and Controller class is given
below to let one understand the kind of class created in the application layer.
The implementation sample given below is the REST API of Todo resource which is the topic of Tutorial (Todo
Application REST).
Aim of the tutorial is to emphasize the saying “Practice makes one perfect”. Prior to detailed explanation,
the user can gain the experience of actually practicing RESTful Web Service development using TERA-
SOLUNA Server Framework for Java (5.x), with the help of this tutorial. When this firsthand experience of
RESTful Web Service development is followed by reading the detailed explanation, the user gains a deeper
understanding of the development.
Especially when the user does not have any experience of RESTful Web Service development, it is recom-
mended to follow a process in the order namely, “Tutorial practice” –> “Detailed explanation of architec-
ture, design and development (described in subsequent sections)” –> “Tutorial revision (Re-practice)”.
Resources handled in the implementation sample (Todo resources) are set in following JSON format.
{
"todoId" : "9aef3ee3-30d4-4a7c-be4a-bc184ca1d558",
"todoTitle" : "Hello World!",
"finished" : false,
"createdAt" : "2014-02-25T02:21:48.493+0000"
}
Resource class is created as the JavaBean representing the Todo resources shown above.
package todo.api.todo;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull
@Size(min = 1, max = 30)
private String todoTitle;
Following five REST APIs (Controller handler methods) are created for Todo resource.
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
// (1)
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<TodoResource> getTodos() {
Collection<Todo> todos = todoService.findAll();
List<TodoResource> todoResources = new ArrayList<>();
for (Todo todo : todos) {
todoResources.add(beanMapper.map(todo, TodoResource.class));
}
return todoResources;
}
// (2)
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public TodoResource postTodos(@RequestBody @Validated TodoResource todoResource) {
Todo createdTodo = todoService.create(beanMapper.map(todoResource, Todo.class));
TodoResource createdTodoResponse = beanMapper.map(createdTodo, TodoResource.class);
return createdTodoResponse;
}
// (3)
@RequestMapping(value="{todoId}", method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public TodoResource getTodo(@PathVariable("todoId") String todoId) {
Todo todo = todoService.findOne(todoId);
TodoResource todoResource = beanMapper.map(todo, TodoResource.class);
return todoResource;
}
// (4)
@RequestMapping(value="{todoId}", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
public TodoResource putTodo(@PathVariable("todoId") String todoId) {
Todo finishedTodo = todoService.finish(todoId);
TodoResource finishedTodoResource = beanMapper.map(finishedTodo, TodoResource.class);
return finishedTodoResource;
}
// (5)
@RequestMapping(value="{todoId}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteTodo(@PathVariable("todoId") String todoId) {
todoService.delete(todoId);
}
5.1.2 Architecture
This section explains the architecture for building a RESTful Web Service.
Resource Oriented Architecture (ROA) is used as the architecture for building RESTful Web Service.
ROA is an abbreviation of “Resource Oriented Architecture” and defines the basic architecture for building a
Web Service in accordance with REST architecture style (rules).
It is important to thoroughly understand ROA architecture when creating RESTful Web Service.
Following five architectural elements must be applied regardless of the application characteristics.
(2) Identifying the resource using URI URI (Universal Resource Identifier) that can uniquely
identify a Web resource is assigned to the resource
published to the client.
(3) Resource operations using HTTP Resource related operations are implemented by using
methods different HTTP methods (GET, POST, PUT and
DELETE).
(4) Using an appropriate format JSON or XML that represents the data structure, is used as
resource format.
(5) Using the appropriate HTTP status Appropriate HTTP status code is set in the response
code returned to the client.
Following two architectural elements are applied depending on the characteristics of an application.
(6) Stateless communication between This element enables to perform the process only by the
client and server information requested from client, without retaining the
application status on the server.
(7) Link to related resource It includes links to other resources (URI) inside a resource
that are related to the specified resource.
It is published as a resource on Web as the means to provide information stored in the system to client.
It signifies that resources can be accessed using HTTP protocol and URI is used as a method to identify resources.
For example, following information is published on the Web as resource, for a Web system providing shopping
site.
• Product information
• Stock information
• Order information
• Member information
URI (Universal Resource Identifier) that can uniquely identify a resource on the Web, is assigned to the
resource to be published to the client.
URL (Uniform Resource Locator), which is a subset of the URI, is actually used.
In ROA, the ability to access a resource on the Web using URI, is called as “Addressability”.
It signifies that on using the same URI, the same resource can be accessed from anywhere.
URI assigned to RESTful Web Service is a combination of “a noun that indicates the type of resource” and “a
value (ID etc.) that uniquely identifies a resource”.
For example, URI of product information handled by a Web system that provides a shopping site, is given below.
• http://example.com/api/v1/items
“items” portion is the “noun that represents the type of resource”. If there are multiple resources, a plural
noun is used.
In the above example, a plural noun is specified to indicate the product information. It forms the URI for
batch operation of product information. If replaced to a file system, it corresponds to a directory.
• http://example.com/api/v1/items/I312-535-01216
The part “I312-535-01216” in the above URI, represents “the value that identifies the resource” and varies
for each resource.
In the above example, product ID is specified as the value for uniquely identifying product information. It
acts as the URI used to handle specific product information. If replaced by a file system, it corresponds to
the files stored in a directory.
Warning: Verbs that indicate operations cannot be included in the URI assigned to RESTful Web Service
are as shown below.
• http://example.com/api/v1/items?get&itemId=I312-535-01216
• http://example.com/api/v1/items?delete&itemId=I312-535-01216
URI mentioned in the above example is not suitable to be assigned to RESTful Web Service since it
includes verbs like get or delete.
In RESTful Web Service, Resource related operations are represented by using HTTP methods
(GET, POST, PUT and DELETE).
Resource operations can be performed by using HTTP methods (GET, POST, PUT, DELETE).
The association of resource operations assigned to HTTP methods and the post-conditions ensured by each oper-
ation, are explained below.
Sr. No. HTTP method Resource operations Post-conditions that the operation
should ensure
(2) POST Resource is created. Server assigns the URI for created
resource, this assigned URI is set to
Location header of response and is
returned to client.
When resource operation is performed using HTTP method, it is necessary to ensure “safety” and “idem-
potency” as post conditions.
[Safety]
It ensures that even if a particular value is multiplied several times by 1, the value does not change.
(for example, if 10 is multiplied several times by 1, result remains 10). This guarantees that even if an
operation is carried out for several times, resource status does not change.
[Idempotency]
It ensures that even if a value is multiplied a number of times by 0, the value remains 0 (for example, if
10 is multiplied a number of times or just once by 0, the result remains 0). This signifies that once an
operation is performed, resource status does not change even if the same operation is performed later
for a number of times. However, when another client is modifying the status of the same resource,
idempotency need not be ensured and can be handled as a precondition error.
Tip: When client specifies the URI assigned to a resource for creating a resource
To create a resource, when the URI to be assigned to the resource is specified by client, PUT method is
called for the URI assigned to the resource to be created.
When creating a resource using PUT method, the general operation is to,
Following is the difference in process images while creating a resource using PUT and POST methods.
(1) PUT method is called by specifying URI (ID) of the resource to be created in URI.
JSON or XML that indicate data structure, are used for resource format.
However, formats other than JSON or XML can also be used depending on the type of resource.
For example, a resource classified as statistical information can be published with line graph represented in
image format (Binary data).
When multiple formats are supported as resource formats, any of the following methods is used to change the
format.
• http://example.com/api/v1/items.json
• http://example.com/api/v1/items.xml
• http://example.com/api/v1/items/I312-535-01216.json
• http://example.com/api/v1/items/I312-535-01216.xml
Appropriate HTTP status code is set in the response to be returned to the client.
Value indicating the method by which server has processed the request received from the client, is set in HTTP
status code.
This is an HTTP specification and it is recommended to conform to the HTTP specifications wherever
possible.
Refer to RFC 2616 (Hypertext Transfer Protocol – HTTP/1.1) - 6.1.1 Status Code and Reason Phrase.
In a traditional Web system wherein HTML is returned in the browser, regardless of the process results, it was
common that "200 OK" was returned as the response and process results were displayed in entity body
(HTML),
In a traditional Web application that returns HTML, there were no issues since an operator (human) determined
the process results.
However, if this structure is used to build a RESTful Web Service, following issues may exist potentially. Hence,
it is recommended to set appropriate status codes.
(1) Even in cases where only the process result (success and failure) is to be determined,
unnecessary process has to be performed, as analysis process is mandatory for entity body.
(2) Since it is mandatory to be aware of the unique error codes defined in the system while handling
errors, it may adversely affect the architecture (design and implementation) at the client side.
(3) Intuitive error analysis may be obstructed when analyzing error causes at client side, since
understanding the meaning of unique error codes defined in the system is required for the same.
In this communication, only the information requested by the client is processed, without retaining the
application status on the server.
In ROA, a state wherein application status is not retained on the server, is called “stateless”.
It signifies that application status is not retained in application server memory (HTTP session etc.) and resource
related operations can be completed only by using the requested information.
In this guideline, it is recommended to retain “stateless” state wherever possible.
Web page transition status, selection status for input value, pull down/checkbox/radio buttons and authen-
tication status etc. are included in application status.
Please note that the “Stateless” state between client and server cannot be retained when the CSRF mea-
sures described in this guideline are implemented for RESTful Web Service as, the token values for CRSF
measures are stored in HTTP sessions.
Following measures need to be implemented for a system that requires high availability.
However, above measures may affect the performance. Hence, it is necessary to consider performance
requirements as well.
Todo
TBD
When high availability is required, it is advisable to review an architecture wherein, “token values for CSRF
measures are stored in a destination other than the AP server memory (HTTP session)”.
Basic architecture is currently under review and will be documented in subsequent versions.
Hypermedia link (URI) to another resource related to the specified resource, is included in the resource.
In ROA, the process of incorporating a hypermedia link for another resource in the resource status display, is
called “Connectivity”.
It signifies that both the linked resources retain this mutual link and all the related resources can be accessed by
following this link.
Connectivity of resources is described below, with the example of member information resource of a shopping
site.
(1) Following JSON is returned when the member information resource is fetched (GET
http://example.com/api/v1/members/M000000001).
{
"memberId" : "M000000001",
"memberName" : "John Smith",
"address" : {
"address1" : "45 West 36th Street",
"address2" : "7th Floor",
"city" : "New York",
872 5 Web Services
"state" : "NY",
"zipCode" : "10018"
},
"links" : [
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
It is not mandatory for a resource to include hypermedia link (URI) to another resource.
When all the endpoints (URI) of REST API are already published, even if the link for related resource is set in
the resource, it is highly unlikely that it will be used.
Particularly, it makes no sense to provide links for the REST API that exchanges resources between systems,
since REST API end points that are already published can be accessed directly.
There is no need to provide a link where it is not required.
In contrast, when a resource is to be directly exchanged between a client application with user interface and
RESTful Web service, the loose coupling between client and server can be enhanced by providing a link.
Following are the reasons for enhancing the coupling between client and server.
(1) Client application needs to know only the logical name of the link in advance. Hence, it is not
necessary to know the specific URI for calling REST API.
(2) Since it is not necessary for the client application to know the specific URI, impact on server,
owing to change in URI, can be minimized.
Whether to provide a hypermedia link (URI) to other resources should be determined on considering all the points
described above.
HATEOAS is an abbreviation for “Hypermedia As The Engine Of Application State” and is one of the architec-
tures for creating a RESTful Web application.
• In the resources (JSON or XML) that are exchanged between client and server, the server includes a hyper-
media link (URI) to an accessible resource.
• Client fetches required resources from the server through the hypermedia link in the resource display (JSON
or XML), and changes application status (screen status etc.).
Therefore, providing a link for related resources is consistent with the HATEOAS architecture.
When loose coupling between server and client is to be enhanced, please review if using the HATEOAS architec-
ture would be beneficial.
Resource extraction
(1) Resource published on the web is used as the information managed by database. However, data
model of the database must not be published as resource as it is, without careful
consideration.
It should be closely investigated, as the fields stored in the database may include some fields
that should not be disclosed to the client.
(2) When information type is different in spite of being managed by the same table of the
database, publishing it as a separate resource may be considered.
There are cases wherein, even if essentially seen as different information, it is managed by the
same table, due to same data structure. Hence, such cases need to be reviewed closely.
(3) In RESTful Web Service, the information operated by an event is extracted as a resource.
The event itself should not be extracted as a resource.
For example, when creating RESTful Web Service to be called from the events (approve, deny,
return etc.) generated by work flow functionality, information for managing the workflow status
or the workflow itself, is extracted as a resource.
Assigning URI
• http://example.com/api/v1/members/M000000001
• http://api.example.com/v1/members/M000000001
It is recommended to include api within the URI domain or path, to clearly indicate that the URI is intended for
RESTful Web Service (REST API).
• http://example.com/api/...
• http://api.example.com/...
It is recommended to include a value that identifies the API version, in the URI to be published to the client, since
it may be necessary to run RESTful Web Service in multiple versions.
Todo
TBD
The 2 URLs given below are assigned for resources that are published on Web.
Following is an example of a URI when publishing member information on Web.
(1) /{Noun that represents collection of /api/v1/members It is the URI used for batch
resources} operations of resources.
(2) /{Noun that represents collection of /api/v1/members/M0001 It is the URI used while
resources/resource identifier (ID operating a specific resource.
etc)}
The URI for related resources published on Web are nested and then displayed.
Following example describes the URI for publishing order information for each member on the Web.
When the related resource published on Web has a single element, the noun that indicates the related resource
should be singular and not plural.
Following is the example of URI to publish credentials of each member on Web.
CRUD operation for resources is published as REST API by assigning the following HTTP methods for the URI
assigned to each resource.
Hereafter, HEAD and OPTIONS methods are described as well. However, providing them for REST API
is optional.
While creating the REST API conforming to HTTP specifications, it is necessary to provide the HEAD
and OPTIONS methods as well. However, it is actually used very rarely and is not required in most of the
cases.
(1) GET REST API that fetches collection of resources specified in URI, is
implemented.
(2) POST REST API that creates and adds the specified resource to the collection is
implemented.
(3) PUT REST API that performs batch update for resource specified in URI is
implemented.
(4) DELETE REST API that performs batch deletion for resource specified in URI is
implemented.
(5) HEAD REST API that fetches meta information of the resource collection
specified in URI, is implemented.
A process same as GET is performed and only header is sent as response.
(6) OPTIONS REST API that responds with the list of HTTP methods (API) supported
by resource collection specified in URI, is implemented.
(1) GET REST API that fetches the resource specified in URI is implemented.
(2) PUT REST API that creates or updates the resource specified in URI is
implemented.
(3) DELETE REST API that deletes the resource specified in URI is implemented.
(4) HEAD A REST API that fetches meta information of the resource specified in
URI is implemented.
A process same as GET is performed and only header is sent as a
response.
(5) OPTIONS REST API that responds with list of HTTP methods (API) supported by
the resource specified in URI is implemented.
Resource format
The JSON sample with field name set in “lower camel case”, is as given below.
In “lower camel case”, first letter of the word is in lowercase and subsequent first letters of words are in
uppercase.
{
"memberId" : "M000000001"
}
JSON sample wherein NULL and blank characters are differentiated, is given below.
{
"dateOfBirth" : null,
"address1" : ""
}
Date format
It is recommended to use extended ISO-8601 format as the JSON date field format.
Format other than extended ISO-8601 format can be used. However, it is advisable to use the extended ISO-8601
format, if there is no particular reason otherwise.
There are two formats in ISO-8601 namely, basic format and extended format, however, readability is higher in
extended format.
1. yyyy-MM-dd
{
"dateOfBirth" : "1977-03-12"
}
2. yyyy-MM-dd’T’HH:mm:ss.SSSZ
{
"lastModifiedAt" : "2014-03-12T22:22:36.637+09:00"
}
{
"lastModifiedAt" : "2014-03-12T13:11:27.356Z"
}
{
"links" : [
{
"rel" : "ownerMember",
"href" : "http://example.com/api/v1/memebers/M000000001"
}
]
}
• Link object consisting of 2 fields - "rel" and "href" is retained in collection format.
• "links" is the field which retains the Link object in collection format.
When an error is detected, it is recommended to use a format that can retain the details of the error occurred.
Detailed error information should be included, especially when there is a possibility of the error being eliminated
owing to re-operation by client.
In contrast, detailed error information should not be included when an event that exposes system vulnerability
occurs. In such cases, the detailed error information should be output to a log.
{
"code" : "e.ex.fw.7001",
"message" : "Validation error occurred on item in the request body.",
"details" : [ {
"code" : "ExistInCodeList",
"message" : "\"genderCode\" must exist in code list of CL_GENDER.",
"target" : "genderCode"
} ]
}
HTTP status code is sent as the response, in accordance with the following guidelines.
(1) When the request is successful, an HTTP status code indicating success or transfer (2xx or 3xx
system) is sent as response.
(2) When the cause of request failure lies at client side, an HTTP status code indicating client error
(4xx system) is sent as the response.
When client is not responsible for request failure however, when the request may be successful
through a re-operation by client, it is still considered as client error.
(3) When the cause of request failure lies at server side, an HTTP status code indicating server
error (5xx system) is sent as the response.
When the request is successful, following HTTP status codes are sent as responses, depending on status.
(1) 200 HTTP status code notifying It is sent as a response when the resource
OK that the request was successful. information corresponding to the request
is output in the entity body of response, as
a result of successful request,
(2) 201 HTTP status code notifying the It is used when a new resource is created
Created creation of a new resource. using POST method.
URI for created resource is set in the
Location header of the response.
(3) 204 HTTP status code notifying a It is sent as a response when the resource
No Content successful request. information corresponding to request is
not output in the entity body of response,
as a result of successful request.
Tip: The difference between "200 OK and "204 No Content" is whether the resource information
is output/not output in the response body.
HTTP status code when the cause of request failure lies at client side
When the cause of request failure lies at client side, following HTTP status codes are sent as responses depending
on the status.
Status codes that must be identified by individual REST APIs handling the resources, are as given below.
(2) 404 HTTP status code notifying It is sent as a response when resource
Not Found that the specified resource does corresponding to specified URI does not
not exist. exist in the system.
(3) 409 HTTP status code notifying It is sent as a response when an exclusive
Conflict that the process is terminated error or a business error is detected.
due to conflict in resource Conflict details and error details required
status when the request status is to resolve the conflict need to be output to
changed by requested contents. the entity body.
Following status codes need not be identified by individual REST APIs which handle the resources.
These status codes need to be identified as the framework or common process.
(5) 406 HTTP status code notifying the It is sent as a response when, the format
Not Acceptable inability to receive a request, as specified in extension or Accept header is
the resource status cannot be not supported as a response format.
sent as a response in the
specified format.
(6) 415 HTTP status code notifying that It is sent as a response when an
Unsupported Media the request cannot be received, unsupported format is specified in
Type as the format specified in entity Content-Type header, as request format.
body is not supported.
HTTP status code when the cause of request failure lies at server side
When the cause of request failure lies at server side, HTTP status codes given below are sent as responses, de-
pending on the status.
Todo
TBD
The guidelines for authentication and authorization control are explained here.
Performing authentication and authorization using OAuth2 protocol will be described in subsequent versions.
Todo
TBD
The process for conditional update (exclusive control) of a resource using HTTP header is explained here.
Conditional update using headers like Etag/Last-Modified-Since etc. will be described in subsequent versions.
Todo
TBD
The process for conditional acquisition (304 not modified control) of resource using HTTP header is explained
here.
Conditional acquisition using headers like Etag/Last-Modified etc. will be described in subsequent versions.
Todo
TBD
Cache control of resources that use headers such as Cache-Control/Pragma/Expires etc. shall be described in
subsequent versions.
Versioning
Todo
TBD
Version control of RESTful Web Service and details on performing parallel operations in multiple versions, will
be described in subsequent versions.
This section explains the basic method to create RESTful Web Service.
While building RESTful Web Service, Web application (war) is built by any one of the following configurations.
It is recommended to build a Web application that is exclusive to RESTful Web Service unless there is a
specific reason otherwise.
(1) Build an exclusive Web It is recommended to build an exclusive Web application (war)
application for RESTful Web for RESTful Web Service when an independence with client
Service. application (UI layer application) that uses RESTful Web
Service, is to be ensured (is necessary).
Client application (UI layer application) described here refers to the application that responds with client
layer (UI layer) component called CSS (Cascading Style Sheets) and scripts like HTML, JavaScript etc.
HTML generated by template engine such as JSP, is also considered.
In Spring MVC, operation settings of the application are defined for each DispatcherServlet. There-
fore, when the requests of RESTful Web Service and client application (UI layer application) are configured
to be received from the same DispatcherServlet, specific operation settings for RESTful Web Ser-
vice or client application cannot be defined, thus resulting in complex or cumbersome settings.
In this guideline, when RESTful Web Service and client application are to be configured as same Web ap-
plication, it is recommended to divide DispatcherServlet to avoid occurrence of the issues described
above.
Configuration image when building a Web application exclusive to RESTful Web Service, is as follows:
Configuration image when building RESTful Web Service and client application as a single application, is as
follows:
pom.xml configuration
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-common-dependencies</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</exclusion>
</exclusions>
</dependency>
Application settings
Warning: DoS attack measures at the time of StAX(Streaming API for XML) use
If the StAX is used to parse the XML format data, protect DoS attack. For details, refer to CVE-2015-3192 -
DoS Attack with XML Input.
Settings for activating the Spring MVC components necessary for RESTful Web Service
• spring-mvc-rest.xml
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="jsonMessageConverter"
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper" />
</bean>
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor"
pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.re
</aop:config>
</beans>
(1) When the value defined in the property file needs to be referred by an application layer
component, the property file should be read by using
<context:property-placeholder> element.
For the details of fetching a value from property file, refer to “Properties Management”.
(2) Add the settings for handling the JSON date field format as extended ISO-8601 format.
Also, when Joda Time class is to be used as a property of JavaBean which represents a resource
(Resource class), “Configuration while using JSR-310 Date and Time API / Joda Time” must be
carried out.
(3) Perform bean registration for the Spring MVC framework component necessary for providing
RESTful Web Service.
JSON can be used as a resource format by performing these settings.
In the above example, resource format is restricted to JSON since the register-defaults attribute
of <mvc:message-converters> element is set as false.
To use XML as resource format, MessageConverter for XML, that performs the XXE
Injection countermeasure, should be specified. For details on designated methods, refer to
“Enabling XXE Injection measures” .
(6) Scan the application layer components for RESTful Web Service (Controller or Helper class
etc.) and perform bean registration.
5.1. RESTful Web Service
The "com.example.project.api" part is the package name for each project. 895
(7) Specify AOP definition to output the exception handled by Spring MVC framework to a log.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Also, when style is to be changed from directly defining a Bean for ObjectMapper to using
Jackson2ObjectMapperFactoryBean, it should be noted that default value for the following configu-
ration differs from the default value of Jackson (disabled).
• MapperFeature#DEFAULT_VIEW_INCLUSION
• DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES
When ObjectMapper operation and default Jackson operation are to be matched, the configuration above is
enabled using featuresToEnable property.
Note: Points to be noted when changing the jackson version from 1.x.x to 2.x.x
• Changed package
version package
1.x.x org.codehaus.jackson
2.x.x com.fasterxml.jackson
• Deprecated List
• http://fasterxml.github.io/jackson-core/javadoc/2.6/deprecated-list.html
• http://fasterxml.github.io/jackson-databind/javadoc/2.6/deprecated-list.html
• http://fasterxml.github.io/jackson-annotations/javadoc/2.6/deprecated-list.html
When RESTful Web Service and client application are built as separate Web applications, the settings are as
follows:
When RESTful Web Service and client application are to be built as same Web application, it is necessary to
perform “Settings when RESTful Web Service and client application are operated as the same Web application” .
• web.xml
<servlet>
<!-- (1) -->
<servlet-name>restAppServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- (2) -->
<param-value>classpath*:META-INF/spring/spring-mvc-rest.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- (3) -->
<servlet-mapping>
<servlet-name>restAppServlet</servlet-name>
<url-pattern>/api/v1/*</url-pattern>
</servlet-mapping>
(1) Specify a name which shows that it is a RESTful Web Service servlet, in <servlet-name>
element.
In the above example, "restAppServlet" is specified as the servlet name.
(2) Specify a Spring MVC bean definition file used to build DispatcherServlet for RESTful
Web Service.
In the above example, META-INF/spring/spring-mvc-rest.xml in class path, is
specified as the Spring MVC bean definition file.
(3) Specify a servlet path pattern to be mapped with the DispatcherServlet of RESTful Web
Service.
In the above example, the servlet path under "/api/v1/" is mapped with the
DispatcherServlet for RESTful Web Service.
Typically, servlet paths like
"/api/v1/"
"/api/v1/members"
"/api/v1/members/xxxxx"
are mapped in the DispatcherServlet("restAppServlet") for RESTful Web
Service.
For the value to be specified in value attribute of @RequestMapping annotation, specify the value as-
signed to the part of wild card (*) in <url-pattern> element.
Note: Domain layer implementation is not explained in this section, however, it is sent as attachment
“Source code of the domain layer class created at the time of REST API implementation”.
Resource format
The resource format of member information should be the following JSON format.
In the following example, although all the fields are displayed, they are not used in the requests and
responses of all API.
For example, "password" is used only in requests whereas "createdAt" or "lastModifiedAt"
are used only in responses.
{
"memberId" : "M000000001",
"firstName" : "Firstname",
"lastName" : "Lastname",
"genderCode" : "1",
"dateOfBirth" : "1977-03-13",
"emailAddress" : "user1@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "user1@test.com",
"password" : "zaq12wsx",
"passwordLastChangedAt" : "2014-03-13T04:39:14.831Z",
"lastModifiedAt" : "2014-03-13T04:39:14.831Z"
},
"createdAt" : "2014-03-13T04:39:14.831Z",
"lastModifiedAt" : "2014-03-13T04:39:14.831Z"
}
Note: This section illustrates an example wherein a hypermedia link for related resource is not provided.
For details on implementation with hypermedia link, refer to “Implementing hypermedia link”.
The specifications for each field of a resource (JSON) are as shown below.
Sr. No. Item name Type I/O speci- Number of Other specifications
fications digits (min-
max)
memberId String I/O 10-10 It should be “Unspecified”
(NULL) at the time of re-
(1)
quest for POST Members.
(2)
(3)
(6) String
(E-mail)
(7)
(8)
(9)
Sr. No. API name HTTP Resource path Status API Overview
Method Code
Note: This section focuses on the details of CRUD operation for a resource. Hence, HEAD and OPTIONS
methods are not explained. To create the RESTful Web Service conforming to HTTP specifications, refer
to “Creating RESTful Web Service conforming to HTTP specifications”.
It is recommended to assign api as the package name for the route package that stores REST API class and to
create a package for each resource (lower case of resource name) under the same.
Resource name in the explanation is Member. Hence, the package name is
org.terasoluna.examples.rest.api.member.
Note: Usually, following 4 types of classes are stored in the created package. It is recommended to use
the following naming rules for name of the class to be created.
• [Resource name]Resource
• [Resource name]RestController
In the explanation, name of the resource is Member. As a result, the respective names will be as below.
• MemberResource
• MemberRestController
• MemberValidator
• MemberHelper
When handling a related resource, it is advisable to place the class for related resource also in the same
package.
It is recommended to create a package named common that stores common parts for REST API just under the
route package that stores the REST API class and to create sub packages at functionality level.
For example, a sub package that stores common parts which perform error handling is created with the name
error.
The class for exception handling created in the subsequent explanation, is stored in a package called
org.terasoluna.examples.rest.api.common.error.
Note: As long as it is clear that the package is storing common parts, it can have a name other than
common.
In this guideline, it is recommended to provide a Resource class as the class representing the resource published
on Web (represent JSON or XML).
The reason for creating a Resource class regardless of DomainObject class (for example, Entity class) being
available is, user interface information (UI) which is used in the I/O with client and information handled
by business process do not necessarily match.
If these are mixed and then used, the application layer may affect the domain layer, resulting in deteriorated
maintainability. It is recommended to create the DomainObject and Resource class separately and convert
data by using BeanMapper like Dozer etc.
(1) To define the data structure of a Define a data structure of the resource published on Web.
resource. Generally, it is very rare to publish the data structure managed
by persistence layer of database etc. as it is, as a resource on
Web.
(2) To define format. Specify a definition related to resource format using annotation.
Annotation to be used differs according to the resource format
(JSON/XML etc.). Jackson annotation is used for JSON format
whereas JAXB annotation is used for XML format.
(3) To define input validation rules. Specify input validation rules for single item of each field by
using Bean Validation annotation.
For input validation details, refer to “Input Validation” .
(1)
• MemberResource.java
package org.terasoluna.examples.rest.api.member;
import java.io.Serializable;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Past;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.springframework.format.annotation.DateTimeFormat;
import org.terasoluna.gfw.common.codelist.ExistInCodeList;
// (1)
public class MemberResource implements Serializable {
// (2)
interface PostMembers {
}
interface PutMember {
}
@Null(groups = PostMembers.class)
@NotEmpty(groups = PutMember.class)
@Size(min = 10, max = 10, groups = PutMember.class)
private String memberId;
@NotEmpty
@Size(max = 128)
private String firstName;
@NotEmpty
@Size(max = 128)
private String lastName;
@NotEmpty
@ExistInCodeList(codeListId = "CL_GENDER")
private String genderCode;
@NotNull
@Past
private LocalDate dateOfBirth;
@NotEmpty
@Size(max = 256)
@Email
private String emailAddress;
@Size(max = 20)
private String telephoneNumber;
@Size(max = 20)
private String zipCode;
@Size(max = 256)
private String address;
@NotNull(groups = PostMembers.class)
@Null(groups = PutMember.class)
@Valid
// (3)
private MemberCredentialResource credential;
@Null
private DateTime createdAt;
@Null
private DateTime lastModifiedAt;
(2) The interface for specifying validation group of Bean Validation is defined.
In this implementation, input validation is grouped, as different input validations are performed
for POST and PUT methods.
Refer to “Input Validation” for grouped validation.
• MemberCredentialResource.java
package org.terasoluna.examples.rest.api.member;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.hibernate.validator.constraints.Email;
import org.joda.time.DateTime;
// (4)
public class MemberCredentialResource implements Serializable {
@Size(max = 256)
@Email
private String signId;
// (5)
@JsonInclude(JsonInclude.Include.NON_NULL)
@NotNull
@Size(min = 8, max = 32)
private String password;
@Null
private DateTime passwordLastChangedAt;
@Null
private DateTime lastModifiedAt;
(4) JavaBean that represents Credential resource which is the related resource of Member resource.
(5) An annotation is specified so that the field itself is not output in JSON when the value is null.
It is specified so that the field ‘password’ is not output in responding JSON.
In the above example, it is restricted to (Inclusion.NON_NULL) for NULL value, however
it can also be specified as (Inclusion.NON_EMPTY) in case of an empty value.
package org.terasoluna.examples.rest.api.member;
// omitted
import org.springframework.web.bind.annotation.RestController;
// omitted
@RequestMapping("members") // (1)
@RestController // (2)
public class MemberRestController {
// omitted ...
An example to create a Controller for REST API by combining @Controller annotation and
@ResponseBody annotation in a conventional way is given below.
@RequestMapping("members")
@Controller
public class MemberRestController {
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Page<MemberResource> getMembers() {
// ...
}
// ...
Example to implement the REST API wherein a page search is performed for member resource collection specified
by URI.
// (1)
public class MembersSearchQuery implements Serializable {
private static final long serialVersionUID = 1L;
// (2)
@NotEmpty
private String name;
(2) Match the property name with the parameter name of request parameter.
In the above example, value "John" is set in the name property of JavaBean for request
/api/v1/members?name=John.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
@Inject
MemberService memberService;
@Inject
Mapper beanMapper;
// (3)
@RequestMapping(method = RequestMethod.GET)
// (4)
@ResponseStatus(HttpStatus.OK)
public Page<MemberResource> getMembers(
// (5)
@Validated MembersSearchQuery query,
// (6)
Pageable pageable) {
// (7)
Page<Member> page = memberService.searchMembers(query.getName(), pageable);
// (8)
List<MemberResource> memberResources = new ArrayList<>();
for (Member member : page.getContent()) {
memberResources.add(beanMapper.map(member, MemberResource.class));
}
Page<MemberResource> responseResource = new PageImpl<>(memberResources,
pageable, page.getTotalElements());
// (9)
return responseResource;
}
// omitted
// omitted
return ResponseEntity.ok().body(responseResource);
}
When it is necessary to change the responding status codes based on process details or pro-
cess results, org.springframework.http.ResponseEntity is used, as shown in the
above implementation.
(7) Call Service method of domain layer and fetch resource information (Entity etc.) matching with
the condition.
For domain layer implementation, refer to “Domain Layer Implementation”.
914 5 Web Services
(8) Generate resource object that retains information published on the Web based on the resource
information matching with the conditions (Entity etc.).
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
{
"content" : [ {
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"genderCode" : "1",
"dateOfBirth" : "1977-03-07",
"emailAddress" : "john.smith@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "john.smit@test.com",
"passwordLastChangedAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
},
"createdAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
}, {
"memberId" : "M000000002",
"firstName" : "Sophia",
"lastName" : "Smith",
"genderCode" : "2",
"dateOfBirth" : "1977-03-07",
"emailAddress" : "sophia.smith@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "sophia.smith@test.com",
"passwordLastChangedAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
},
"createdAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
} ],
"last" : false,
"totalPages" : 13,
"totalElements" : 25,
"size" : 2,
"number" : 1,
"sort" : [ {
"direction" : "DESC",
"property" : "lastModifiedAt",
"ignoreCase" : false,
"nullHandling": "NATIVE",
"ascending" : false
} ],
"numberOfElements" : 2,
"first" : false
}
Note: Points to be noted due to changes in API specifications of Spring Data Commons
Specifically,
• In Page interface and PageImpl class, isFirst() and isLast() methods are added in spring-
data-commons 1.8.0.RELEASE, and isFirstPage() and isLastPage() methods are deleted
from spring-data-commons 1.9.0.RELEASE.
When using Page interface (PageImpl class) as resource object of REST API, that application may also
need to be modified, as JSON and XML format get changed.
<mapping type="one-way">
<class-a>org.terasoluna.examples.rest.domain.model.MemberCredential</class-a>
<class-b>org.terasoluna.examples.rest.api.member.MemberCredentialResource</class-b>
<!-- (12) -->
<field-exclude>
<a>password</a>
<b>password</b>
</field-exclude>
</mapping>
</mappings>
(11) Create a file that defines mapping rules for Member object and MemberResource object.
It is recommended to create a mapping definition file of Dozer for each resource.
(12) In the above example, password field is not copied while copying the details of
MemberCredential which is a related entity of Member, to
MemberCredentialResource, a related resource of MemberResource.
For Bean mapping definition methods, refer to “Bean Mapping (Dozer)” .
• Request example
• Response Example
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: fb63a6d446f849feb8ccaa4c9a794333
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 13 Mar 2014 11:10:43 GMT
{"content":[{"memberId":"M000000001","firstName":"John","lastName":"Smith","genderCode":"1","
Tip: When page search is not necessary, Resource class list may be handled directly.
Following is the definition of the Controller method used when handling the list of Resource class directly.
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<MemberResource> getMembers(
@Validated MembersSearchQuery query) {
// omitted
}
[ {
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"genderCode" : "1",
"dateOfBirth" : "1977-03-07",
"emailAddress" : "john.smith@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "john.smit@test.com",
"passwordLastChangedAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
},
"createdAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
}, {
"memberId" : "M000000002",
"firstName" : "Sophia",
"lastName" : "Smith",
"genderCode" : "2",
"dateOfBirth" : "1977-03-07",
"emailAddress" : "sophia.smith@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "sophia.smith@test.com",
"passwordLastChangedAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
},
"createdAt" : "2014-03-13T10:18:08.003Z",
"lastModifiedAt" : "2014-03-13T10:18:08.003Z"
} ]
Example of implementation of REST API wherein a specified Member resource is created and added to the
collection is given below.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
// (1)
@RequestMapping(method = RequestMethod.POST)
// (2)
@ResponseStatus(HttpStatus.CREATED)
public MemberResource postMember(
// (3)
@RequestBody @Validated({ PostMembers.class, Default.class })
MemberResource requestedResource) {
// (4)
Member inputMember = beanMapper.map(requestedResource, Member.class);
Member createdMember = memberService.createMember(inputMember);
return responseResource;
}
// omitted
(2) Assign @ResponseStatus annotation as method annotation and specify responding status
code.
Set 201(Created) in the value attribute of @ResponseStatus annotation.
(3) Specify JavaBean (Resource class) that receives information of newly created resource as an
argument.
Assign @org.springframework.web.bind.annotation.RequestBody as
argument annotation.
By assigning @RequestBody annotation, JSON or XML data set in request Body is
unmarshalled in Resource object.
Assign @Validated annotation as argument annotation to enable input validation. For details
on input validation, refer to “Input Validation” .
(4) Call Service method of domain layer and create a new resource.
For domain layer implementation, refer to “Domain Layer Implementation”.
• Request example
User-Agent: Java/1.7.0_51
Host: localhost:8080
Connection: keep-alive
Content-Length: 248
{"firstName":"John","lastName":"Smith","genderCode":"1","dateOfBirth":"2013-03-13","emailAddr
• Response example
{"memberId":"M000000023","firstName":"John","lastName":"Smith","genderCode":"1","dateOfBirth"
Implementation of REST API that fetches the Member resource specified by URI, is shown below.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
// (1)
@RequestMapping(value = "{memberId}", method = RequestMethod.GET)
// (2)
@ResponseStatus(HttpStatus.OK)
public MemberResource getMember(
// (3)
@PathVariable("memberId") String memberId) {
// (4)
return responseResource;
}
// omitted
(1) Specify path variable ({memberId} in the example above) in value attribute whereas
RequestMethod.GET in method attribute of @RequestMapping annotation.
A value that uniquely identifies the resource is specified in {memberId}.
(2) Assign @ResponseStatus annotation as method annotation and specify the responding
status code.
Set 200 (OK) in value attribute of @ResponseStatus annotation.
(3) Fetch the value that uniquely identifies the resource from path variable.
Value specified in path variable ({memberId}) can be received as method argument by
specifying @PathVariable("memberId") as argument annotation.
For details on path variable, refer to “Retrieving values from URL path”.
In the above example, when URI is /api/v1/members/M12345, "M12345" is stored in
memberId of argument.
(4) Call Service method of domain layer and acquire the resource information (Entity etc.) that
matches with the ID fetched from path variable.
For domain layer implementation, refer to “Domain Layer Implementation”.
• Request example
• Response Example
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: 275b4e7a61f946eea47672f272315ac2
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 13 Mar 2014 11:25:13 GMT
{"memberId":"M000000003","firstName":"John","lastName":"Smith","genderCode":"1","dateOfBirth"
Implementation of REST API that updates the Member resource specified in URI, is shown below.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
// (1)
@RequestMapping(value = "{memberId}", method = RequestMethod.PUT)
// (2)
@ResponseStatus(HttpStatus.OK)
public MemberResource putMember(
@PathVariable("memberId") String memberId,
// (3)
@RequestBody @Validated({ PutMember.class, Default.class })
MemberResource requestedResource) {
// (4)
Member inputMember = beanMapper.map(
requestedResource, Member.class);
Member updatedMember = memberService.updateMember(
memberId, inputMember);
return responseResource;
}
// omitted
(1) Specify path variable ({memberId}in the example above) in value attribute whereas
RequestMethod.PUT in “method” attribute of @RequestMapping annotation.
Value that uniquely identifies the resource is specified in {memberId}.
(3) Specify JavaBean (Resource class) for receiving the details of resource update as an argument.
By assigning @RequestBody annotation as argument annotation, JSON or XML data set in
request Body is unmarshalled in Resource object.
(4) Call Service method of domain layer and update the resource information (Entity etc.)
matching with the ID fetched from path variable.
For domain layer implementation, refer to “Domain Layer Implementation”.
• Request example
{"memberId":"M000000004","firstName":"John","lastName":"Smith","genderCode":"1","dateOfBirth"
• Response example
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: 5e8fea3aae044e94bf20a293e155af57
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 13 Mar 2014 11:35:59 GMT
{"memberId":"M000000004","firstName":"John","lastName":"Smith","genderCode":"1","dateOfBirth"
Implementation of REST API that deletes the Member resource specified by URI is as follows:
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
// (1)
@RequestMapping(value = "{memberId}", method = RequestMethod.DELETE)
// (2)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteMember(
@PathVariable("memberId") String memberId) {
// (3)
memberService.deleteMember(memberId);
// omitted
(1) Specify path variable ({memberId}in the example above) in value attribute and
RequestMethod.DELETE in method attribute of @RequestMapping annotation.
(2) Assign @ResponseStatus annotation as method annotation and specify the responding
status code.
Set 204 (NO_CONTENT) in value attribute of @ResponseStatus annotation.
(3) Call Service method of domain layer and delete resource information (Entity etc.) matching
with the ID fetched from path variable.
For domain layer implementation, refer to “Domain Layer Implementation”.
Note: To set deleted resource information in response BODY, set (200) OK in the status code.
• Request example
• Response example
How to handle the exceptions occurring in RESTful Web Service is explained below.
RESTful Web Service oriented generic exception handling feature is not provided in Spring MVC.
Alternately,
(org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler)
is provided as the class that assists in implementing exception handling for RESTful Web Service.
This guideline recommends a common exception handling method, by creating an exception handling class that
inherits the Spring MVC class and assigning @ControllerAdvice annotation to this exception handling
class.
(1) Spring MVC Spring MVC receives a request from client and calls REST API.
(2) (Framework)
(5) Custom Exception An error object that retains error information is generated in the exception
Handler handling class and returned to Spring MVC.
(Common
Component)
(6) Spring MVC Spring MVC converts the error object to JSON format message using
(Framework) HttpMessageConverter.
(7) Spring MVC sets the JSON format error message in response BODY and
sends response to the client.
{
"code" : "e.ex.fw.7001",
"message" : "Validation error occurred on item in the request body.",
"details" : [ {
"code" : "ExistInCodeList",
"message" : "\"genderCode\" must exist in code list of CL_GENDER.",
"target" : "genderCode"
} ]
}
package org.terasoluna.examples.rest.api.common.error;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
// (1)
public class ApiError implements Serializable {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final String target; // (2)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final List<ApiError> details = new ArrayList<>(); // (3)
return message;
}
(2) Field that retains the value for identifying the target where error has occurred.
When an error occurs in input validation, there are cases where the value that identifies the field
where error has occurred needs to be returned to the client.
In such cases, it is necessary to set a field that retains the field name where error has occurred.
Tip: When the value is null or empty, it is possible to avoid fields being output to JSON
by specifying @JsonInclude(JsonInclude.Include.NON_EMPTY) in the field. When
the condition to disable field output is to be restricted to null, it is advisable to specify
@JsonInclude(JsonInclude.Include.NON_NULL).
Refer to Appendix for the source code when implementation of all exception handling is completed.
// (4)
@Component
public class ApiErrorCreator {
@Inject
MessageSource messageSource;
// omitted
(4) If needed, create a class that provides the method for generating error information.
Creating this class is not mandatory. However, it is recommended to create it so as to clearly
define the role division.
The reason for using WebRequest as an argument instead of java.util.Locale is due to an addi-
tional requirement wherein, HTTP request details are to be embedded in the error message. When there is
no such requirement to embed HTTP request details in error, Locale can also be used.
Refer to Appendix for the source code when implementation for all exception handling is completed.
@ControllerAdvice // (6)
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
ApiErrorCreator apiErrorCreator;
@Inject
ExceptionCodeResolver exceptionCodeResolver;
// (7)
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
final Object apiError;
// (8)
if (body == null) {
String errorCode = exceptionCodeResolver.resolveExceptionCode(ex);
apiError = apiErrorCreator.createApiError(request, errorCode, ex
.getLocalizedMessage());
} else {
apiError = body;
}
// (9)
return ResponseEntity.status(status).headers(headers).body(apiError);
}
// omitted
(8) When the JavaBean output to response Body is not specified, generate a JavaBean object that
retains error information.
In the above example, the exception class changes the error code by using
ExceptionCodeResolver provided by common library.
For setting example of ExceptionCodeResolver, refer to “Resolving error codes and
messages using ExceptionCodeResolver” .
When the JavaBean output to response Body is specified, use the specified JavaBean as it is.
This process is implemented considering that error information is generated individually in the
error handling process for each exception.
(9) Set the error information generated in (8) in the ‘Body’ of HTTP Entity for response and then
return the same.
Error information thus returned is converted to JSON using framework and sent as a response.
• Response example
Implementation for responding to input errors (syntax error, unit item check error, correlated field check error) is
explained here.
(1) org.springframework.web.bind. This exception occurs when there is an error during the
MethodArgumentNotValidException input validation for JSON or XML specified in request
BODY.
Basically, it occurs when an invalid value is entered in the
resource that is specified when POST or PUT method is
performed for the resource.
(2) org.springframework.validation. This exception occurs when there is an error during the
BindException input validation for request parameter (key=query string
of value format).
Basically, it occurs when an invalid value is entered in the
search conditions specified at the time of GET method of
resource collection.
When following annotations are specified as arguments of Controller handler method (argument other than
String), TypeMismatchException may occur.
• @org.springframework.web.bind.annotation.RequestParam
• @org.springframework.web.bind.annotation.RequestHeader
• @org.springframework.web.bind.annotation.Pathvariable
• @org.springframework.web.bind.annotation.MatrixVariable
Refer to “Resolving error codes and messages using ExceptionCodeResolver” in order to resolve the error
@Component
public class ApiErrorCreator {
@Inject
MessageSource messageSource;
// omitted
// (1)
public ApiError createBindingResultApiError(WebRequest request,
String errorCode, BindingResult bindingResult,
String defaultErrorMessage) {
ApiError apiError = createApiError(request, errorCode,
defaultErrorMessage);
for (FieldError fieldError : bindingResult.getFieldErrors()) {
apiError.addDetail(createApiError(request, fieldError, fieldError
.getField()));
}
for (ObjectError objectError : bindingResult.getGlobalErrors()) {
apiError.addDetail(createApiError(request, objectError, objectError
.getObjectName()));
}
return apiError;
}
// (2)
private ApiError createApiError(WebRequest request,
DefaultMessageSourceResolvable messageResolvable, String target) {
String localizedMessage = messageSource.getMessage(messageResolvable,
request.getLocale());
return new ApiError(messageResolvable.getCode(), localizedMessage, target);
}
// omitted
(2) A common method is created since same process is implemented for single field check error
(FieldError) and correlation check error (ObjectError).
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
ApiErrorCreator apiErrorCreator;
@Inject
ExceptionCodeResolver exceptionCodeResolver;
// omitted
// (3)
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleBindingResult(ex, ex.getBindingResult(), headers, status,
request);
}
// (4)
@Override
protected ResponseEntity<Object> handleBindException(BindException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleBindingResult(ex, ex.getBindingResult(), headers, status,
request);
}
// (5)
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
if (ex.getCause() instanceof Exception) {
return handleExceptionInternal((Exception) ex.getCause(), null,
headers, status, request);
} else {
return handleExceptionInternal(ex, null, headers, status, request);
}
}
// omitted
// (6)
protected ResponseEntity<Object> handleBindingResult(Exception ex,
BindingResult bindingResult, HttpHeaders headers,
HttpStatus status, WebRequest request) {
String code = exceptionCodeResolver.resolveExceptionCode(ex);
String errorCode = exceptionCodeResolver.resolveExceptionCode(ex);
ApiError apiError = apiErrorCreator.createBindingResultApiError(
request, errorCode, bindingResult, ex.getMessage());
return handleExceptionInternal(ex, apiError, headers, status, request);
}
// omitted
400 (Bad Request) is set in the status code and presence of some flaw in the field value of the
specified resource is notified.
400 (Bad Request) is set in the status code and presence of flaw in the specified request
parameter is notified.
400 (Bad Request) is set in the status code and presence of flaw in the specified resource
format etc. is notified
(6) Generate a JavaBean object that retains error information for input validation error.
In the above example, this method is created as a common method since same process is
implemented in handleMethodArgumentNotValid and handleBindException.
When JSON is used as the resource format, following exception is stored as cause exception of
HttpMessageNotReadableException.
• Following error response is sent when an input validation error (single field check error, correlated field
check error) occurs.
• Following error response is sent when JSON errors (format error etc.) occur.
When a resource does not exist, implementation for responding to the “resource not found” error, is explained
below.
When a resource matching with the ID fetched from path variable is not found, generate an exception notifying
“resource not found” .
org.terasoluna.gfw.common.exception.ResourceNotFoundException is provided by
common library as an exception notifying “resource not found”.
Implementation is as given below.
• When a resource matching with the ID fetched from path variable is not found, generate
ResourceNotFoundException.
@Component
public class ApiErrorCreator {
// omitted
// (1)
public ApiError createResultMessagesApiError(WebRequest request,
String rootErrorCode, ResultMessages resultMessages,
String defaultErrorMessage) {
ApiError apiError;
if (resultMessages.getList().size() == 1) {
ResultMessage resultMessage = resultMessages.iterator().next();
String errorCode = resultMessage.getCode();
String errorText = resultMessage.getText();
if (errorCode == null && errorText == null) {
errorCode = rootErrorCode;
}
apiError = createApiError(request, errorCode, errorText,
resultMessage.getArgs());
} else {
apiError = createApiError(request, rootErrorCode,
defaultErrorMessage);
for (ResultMessage resultMessage : resultMessages.getList()) {
apiError.addDetail(createApiError(request, resultMessage
.getCode(), resultMessage.getText(), resultMessage
.getArgs()));
}
}
return apiError;
}
// omitted
(1) Create a method for generating error information from process results.
In the above example, the message information retained by ResultMessages is set in error
information.
Note: In the above example, as ResultMessages can retain multiple messages, the process is divided
as per when a single message is stored and when multiple messages are stored.
When it is not necessary to support multiple messages, the process wherein the message at the start is
• Create a method for handling the exception that notifies “resource not found” error, in the class that per-
forms error handling.
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
ApiErrorCreator apiErrorCreator;
@Inject
ExceptionCodeResolver exceptionCodeResolver;
// omitted
// (2)
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.NOT_FOUND, request);
}
// omitted
// (3)
private ResponseEntity<Object> handleResultMessagesNotificationException(
ResultMessagesNotificationException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
String errorCode = exceptionCodeResolver.resolveExceptionCode(ex);
ApiError apiError = apiErrorCreator.createResultMessagesApiError(
request, errorCode, ex.getResultMessages(), ex.getMessage());
return handleExceptionInternal(ex, apiError, headers, status, request);
}
// omitted
Set 404 (Not Found) in the status code and notify a message stating, ‘specified resource does
not exist in the server’.
(3) Generate a JavaBean object that retains error information for “resource not found” error and
business error.
In the above example, this method is created as a common method since the process is same as
that for error handling of business error discussed hereafter.
An implementation wherein business error is sent as a response on detecting violation of business rule, is explained
here.
Perform business rule check as Service process and generate business exception when a business rule violation is
detected. For details on how to detect business error, refer to “Notifying business error”.
• Create a method to handle business exception in the class that performs error handling.
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
// omitted
// (1)
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex,
WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
// omitted
Set 409 (Conflict)in the status code and send a message notifying although there are no errors
in the resource itself specified by client, all the conditions necessary for operating the resource
stored by the server are not in place.
Transfer-Encoding: chunked
Date: Wed, 19 Feb 2014 09:03:26 GMT
An implementation is explained here wherein, an exclusive error is generated and sent as a response.
Exclusive error handling is necessary when performing exclusive control.
For details of exclusive control, refer to “Exclusive Control” .
• Create a method for exclusive error handling in the class that performs error handling.
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
// omitted
// (1)
@ExceptionHandler({ OptimisticLockingFailureException.class,
PessimisticLockingFailureException.class })
public ResponseEntity<Object> handleLockingFailureException(Exception ex,
WebRequest request) {
return handleExceptionInternal(ex, null, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
// omitted
Set 409(Conflict) in status code and send a message notifying that, ‘although there are no flaws
in the resource itself specified by client, the conditions for operating the resource could not be
fulfilled due to conflict in the process’.
An implementation wherein system error is sent as a response on detecting system abnormality, is explained here.
Generate system exception when any system abnormality is detected. Refer to “Notifying system error” for the
details on how to detect system errors.
• Create a method to handle system exceptions in the class that performs error handling.
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
// omitted
// (1)
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleSystemError(Exception ex,
WebRequest request) {
return handleExceptionInternal(ex, null, new HttpHeaders(),
HttpStatus.INTERNAL_SERVER_ERROR, request);
}
// omitted
If ExceptionCodeResolver provided by common library is used, error codes can be resolved from the
exception class.
Especially, this functionality proves to be convenient, when the cause of error lies at client side and when it is
necessary to set the error message corresponding to error cause.
• applicationContext.xml
Mapping exception class and error code (exception code).
<bean id="exceptionCodeResolver"
class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
<property name="exceptionMappings">
<map>
<!-- omitted -->
<entry key="ResourceNotFoundException" value="e.ex.fw.5001" />
<entry key="HttpRequestMethodNotSupportedException" value="e.ex.fw.6001" />
<entry key="MediaTypeNotAcceptableException" value="e.ex.fw.6002" />
<entry key="HttpMediaTypeNotSupportedException" value="e.ex.fw.6003" />
<entry key="MethodArgumentNotValidException" value="e.ex.fw.7001" />
<entry key="BindException" value="e.ex.fw.7002" />
<entry key="JsonParseException" value="e.ex.fw.7003" />
<entry key="UnrecognizedPropertyException" value="e.ex.fw.7004" />
<entry key="JsonMappingException" value="e.ex.fw.7005" />
<entry key="TypeMismatchException" value="e.ex.fw.7006" />
<entry key="BusinessException" value="e.ex.fw.8001" />
<entry key="OptimisticLockingFailureException" value="e.ex.fw.8002" />
<entry key="PessimisticLockingFailureException" value="e.ex.fw.8002" />
<entry key="DataAccessException" value="e.ex.fw.9002" />
<!-- omitted -->
</map>
</property>
• xxx-web/src/main/resources/i18n/application-messages.properties
Message corresponding to error code (exception code) is set for the error that occurs in application layer.
# ---
# Application common messages
# ---
e.ex.fw.5001 = Resource not found.
# omitted
• xxx-web/src/main/resources/ValidationMessages.properties
Set a message corresponding to error code for the error that occurs in the input validation performed using
Bean Validation.
# ---
# Bean Validation common messages
# ---
• xxx-domain/src/main/resources/i18n/domain-messages.properties
Set a message corresponding to error code (exception code) for the error in domain layer.
# omitted
When an error occurs in Filter or when error responses are sent by using
HttpServletResponse#sendError, the errors cannot be handled using exception handling feature
of Spring MVC. Hence, these errors are notified to Servlet Container.
This section explains how to handle the errors notified to Servlet Container.
(1) Servlet Container Servlet Container receives a request from the client and performs process.
(AP Server) Servlet Container detects an error during the process.
(2) Servlet Container performs error handling according to the error page
definition in web.xml.
If the error is not fatal, Controller for error handling is called and the error
is handled.
(2’) In case of a fatal error, a static JSON file provided in advance is fetched
and a response is sent to the client.
(3) Spring MVC Spring MVC calls the Controller that performs error handling.
(Framework)
(4) Controller An error object that retains error information is generated in the Controller
(Common and is returned to Spring MVC.
Component)
(5) Spring MVC Spring MVC converts the error object to JSON format message by using
(Framework) HttpMessageConverter.
(6) Spring MVC sets the JSON format error message in response BODY and
sends a response to client.
Create a controller that sends the error response for the error notified to Servlet Container.
package org.terasoluna.examples.rest.api.common.error;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import javax.servlet.RequestDispatcher;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
// (1)
@RequestMapping("error")
@RestController
public class ApiErrorPageController {
@Inject
ApiErrorCreator apiErrorCreator; // (2)
// (3)
private final Map<HttpStatus, String> errorCodeMap = new HashMap<HttpStatus, String>();
// (4)
public ApiErrorPageController() {
errorCodeMap.put(HttpStatus.NOT_FOUND, "e.ex.fw.5001");
}
// (5)
@RequestMapping
public ResponseEntity<ApiError> handleErrorPage(WebRequest request) {
// (6)
HttpStatus httpStatus = HttpStatus.valueOf((Integer) request
.getAttribute(RequestDispatcher.ERROR_STATUS_CODE,
RequestAttributes.SCOPE_REQUEST));
// (7)
String errorCode = errorCodeMap.get(httpStatus);
// (8)
ApiError apiError = apiErrorCreator.createApiError(request, errorCode,
httpStatus.getReasonPhrase());
// (9)
return ResponseEntity.status(httpStatus).body(apiError);
}
(3) Create Map for mapping HTTP status code and error code.
Creating a static JSON file to be sent as response when a fatal error occurs
Create a static JSON file to be sent as a response when a fatal error occurs.
• unhandledSystemError.json
Settings for handling an error that is notified to Servlet Container are explained here.
• web.xml
Note: In Servlet specification,, the behaviour wherein a path is specified which assigns query parameters
in <location> of <error-page> is not defined. Hence, behaviour is likely to change according to
AP server. Accordingly, it is not recommended to transfer information to transition destination at the time
of error using a query parameter.
• When the request is sent to a non-existing path, following error response is sent.
Security measures
Todo
TBD
How to implement authentication and authorization using OAuth2 (Spring Security OAuth2), will be explained in
subsequent versions.
CSRF measures
• Refer to CSRF Measures for the setting methods when CSRF measures are carried out for RESTful Web
Service.
• Refer to Disabling CSRF measures for the setting methods when CSRF measures are not carried out for
RESTful Web Service.
Todo
TBD
How to implement conditional process control using headers like Etag etc. will be explained in subsequent ver-
sions.
Todo
TBD
How to implement cache control using headers like Cache-Control/Expires/Pragma etc. will be explained in
subsequent versions.
The properties those belong to the specified group alone can be output by specifying the group in Controller.
1 property may also belong to multiple groups.
Example of implementation when Member resource is handled in 2 formats of ‘Overview’ and ‘Details’ is as
follows.
‘Overview format’ outputs the main item of Member resource and ‘Details format’ outputs all items of Member
resource.
• MemberResource.java
package org.terasoluna.examples.rest.api.member;
import java.io.Serializable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonView;
// (1)
interface Summary {
}
// (2)
interface Detail {
}
// (3)
@JsonView({Summary.class, Detail.class})
private String memberId;
@JsonView({Summary.class, Detail.class})
private String firstName;
@JsonView({Summary.class, Detail.class})
private String lastName;
// (4)
@JsonView(Detail.class)
private String genderCode;
@JsonView(Detail.class)
private LocalDate dateOfBirth;
@JsonView(Detail.class)
private String emailAddress;
@JsonView(Detail.class)
private String telephoneNumber;
@JsonView(Detail.class)
private String zipCode;
@JsonView(Detail.class)
private String address;
// (5)
private DateTime createdAt;
(1) A marker interface to specify the group which controls the output is defined.
A group to be specified at the time of overview output is defined in the above example.
(2) A marker interface to specify the group which controls the output is defined.
A group to be specified at the time of details output is defined in the above example.
(3) The items to be output in multiple groups can be assigned to multiple groups by setting
arguments to an array and passing multiple marker interfaces.
Since the items in the above example are to be assigned to both the groups, overview and
details, 2 marker interfaces are set as arguments.
(4) The items to be output in single group can be assigned to the corresponding group
by setting the marker interface to argument.
Since there is 1 element in this case, it is not necessary to set it in the array.
Since the items in the above example are to be assigned only to the details group, 1 marker
interface is set to an argument.
(5) @JsonView is not set in the items those do not belong to the group.
It can be changed whether the items those do not belong to the group are to be output,
according to the settings.
Method of setting is described later.
• MemberRestController.java
package org.terasoluna.examples.rest.api.member;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.terasoluna.examples.rest.domain.model.Member;
import org.terasoluna.examples.rest.domain.service.member.MemberService;
import com.fasterxml.jackson.annotation.JsonView;
@RequestMapping("members")
@RestController
public class MemberRestController {
@Inject
MemberService memberService;
@Inject
Mapper beanMapper;
// (1)
@JsonView(Summary.class)
@RequestMapping(value = "{memberId}", params = "format=summary", method = RequestMethod.G
@ResponseStatus(HttpStatus.OK)
public MemberResource getMemberSummary(@PathVariable("memberId") String memberId) {
return responseResource;
}
// (2)
@JsonView(Detail.class)
@RequestMapping(value = "{memberId}", params = "format=detail", method = RequestMethod.GE
@ResponseStatus(HttpStatus.OK)
public MemberResource getMemberDetail(@PathVariable("memberId") String memberId) {
return responseResource;
}
(1) Attach @JsonView and set the marker interface for the group to be output.
Set Summary marker interface in the method that outputs the overview.
(2) Set Detail marker interface in the method that outputs the details.
Properties those belong to the group specified in Controller alone are displayed in the body that is output.
The output example is as follows.
• Summary
{
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"createdAt" : "2014-03-14T11:02:41.477Z",
"lastModifiedAt" : "2014-03-14T11:02:41.477Z"
}
• Detail
{
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"genderCode" : "1",
"dateOfBirth" : "2013-03-14",
"emailAddress" : "user1394794959984@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"createdAt" : "2014-03-14T11:02:41.477Z",
"lastModifiedAt" : "2014-03-14T11:02:41.477Z"
}
</property>
</bean>
• Summary
{
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith"
}
• Detail
{
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"genderCode" : "1",
"dateOfBirth" : "2013-03-14",
"emailAddress" : "user1394794959984@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo"
}
Note: @JsonView is created using the following 2 functions. These are the functions those can be used when
the common processes before and after object mapping are to be implemented in the process methods to which
@RequestMapping in the Controller is added.
• org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice
• org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
@ControllerAdvice can be applied by adding it to the implementation class of these interfaces. Refer Im-
plementing “@ControllerAdvice” for the details of @ControllerAdvice.
• org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice
(1) supports Determine whether this Advice is applied to the request sent. It is applied
if true.
(2) handleEmptyBody It is called before the contents of request body are reflected in the object
used in Controller and when the body is empty.
(3) beforeBodyRead It is called before the contents of request body are reflected in the object
used in Controller.
(4) afterBodyRead It is called after the contents of request body are reflected in the object
used in Controller.
When it is not required to describe the processes with all above timings, they can be easily implemented by inherit-
ing org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter
implemented with the above methods except supports not performing any action and overriding only the necessary
portion.
• org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
(1) supports Determine whether this Advice is applied to the request sent. It is applied
if true.
(2) beforeBodyWrite It is called before reflecting the return value in the response after the
process in Controller is completed.
5.1.6 Appendix
Configuration while using JSR-310 Date and Time API / Joda Time
When JSR-310 Date and Time API is to be used as a property of JavaBean which represents a resource (Resource
class), it is not necessary to add a dependency relation since the dependency relation is defined in terasoluna-gfw-
common-dependencies. On the contrary, when Joda Time class is to be used, an extension module provided by
Jackson in pom.xml is added to dependent library.
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-jodatime-dependencies</artifactId>
<type>pom</type>
</dependency>
or
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
• Objects that are set as Proxy by using Lazy Load function of Hibernate ORM
Settings when RESTful Web Service and client application are operated as the same Web appli-
cation
When RESTful Web Service and client application are built as same Web application, it is recommended to
divide as DispatcherServlet that receives the requests for RESTful Web Service and
DispatcherServlet that receives client application requests.
How to divide DispatcherServlet is explained below.
• web.xml
(1) Request mapping with the DispatcherServlet that receives request for client application.
(2) Add Servlet (DispatcherServlet) that receives the request for RESTful Web Service.
Specify a name indicating it as RESTful Web Service Servlet in <servlet-name> element.
In the above example, "restAppServlet" is specified as the Servlet name.
(3) Specify Spring MVC bean definition file to be used while building the
DispatcherServlet for RESTful Web Service.
In the above example, META-INF/spring/spring-mvc-rest.xml in class path, is
specified as the Spring MVC bean definition file.
(4) Specify the pattern of servlet path mapped to the DispatcherServletfor RESTful Web
Service.
In the above example, the Servlet path under "/api/v1/" is mapped in the
DispatcherServlet for RESTful Web Service.
Typically, Servlet paths like
"/api/v1/"
"/api/v1/members"
"/api/v1/members/xxxxx"
are mapped in the DispatcherServlet ("restAppServlet") for RESTful Web
Service.
Value in the wild card (*) part specified in <url-pattern> element, is specified as the value in “value”
attribute of @RequestMapping annotation.
of same.
Implementation for including a hypermedia link to a related resource in JSON, is explained here.
package org.terasoluna.examples.rest.api.common.resource;
import java.io.Serializable;
// (1)
public class Link implements Serializable {
(1) Create a JavaBean for link information that retains the link name and URL.
package org.terasoluna.examples.rest.api.common.resource;
import java.net.URI;
import java.util.LinkedHashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonInclude;
// (2)
public abstract class AbstractLinksSupportedResource {
// (3)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final Set<Link> links = new LinkedHashSet<>();
// (4)
public AbstractLinksSupportedResource addLink(String rel, URI href) {
links.add(new Link(rel, href.toString()));
return this;
}
// (5)
public AbstractLinksSupportedResource addSelf(URI href) {
return addLink("self", href);
}
// (5)
public AbstractLinksSupportedResource addParent(URI href) {
return addLink("parent", href);
}
(2) Create an abstract class (JavaBean) of the Resource that retains collection of link information.
It is assumed that this is the class inherited by the Resource class supporting hypermedia link.
• Inherit an abstract class of Resource that retains collection of link information in Resource class.
package org.terasoluna.examples.rest.api.member;
// (1)
public class MemberResource extends
AbstractLinksSupportedResource implements Serializable {
// omitted
(1) Inherit an abstract class of Resource that retains collection of link information.
By this, the field (links) that retains collection of link information is imported, thus making it
a Resource class that supports hypermedia link.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
// (3)
responseResource.addSelf(uriBuilder.path("/members").pathSegment(memberId)
.build().toUri());
return responseResource;
}
// omitted
Method that builds an URI to be set in link information should be implemented as required.
• Response example
Following response is obtained in actual operation.
{
"links" : [ {
"rel" : "self",
"href" : "http://localhost:8080/rest-api-web/api/v1/members/M000000001"
} ],
"memberId" : "M000000001",
"firstName" : "John",
"lastName" : "Smith",
"genderCode" : "1",
"dateOfBirth" : "2013-03-14",
"emailAddress" : "user1394794959984@test.com",
"telephoneNumber" : "09012345678",
"zipCode" : "1710051",
"address" : "Tokyo",
"credential" : {
"signId" : "user1394794959984@test.com",
"passwordLastChangedAt" : "2014-03-14T11:02:41.477Z",
"lastModifiedAt" : "2014-03-14T11:02:41.477Z"
},
"createdAt" : "2014-03-14T11:02:41.477Z",
"lastModifiedAt" : "2014-03-14T11:02:41.477Z"
}
A part of REST API implementation explained in this version does not conform to HTTP specifications.
This section explains the implementation that creates the RESTful Web Service conforming to HTTP
specifications.
When conforming to HTTP specifications, it is necessary to set the URI of created resource in the response
header of POST method (“Location header”).
Implementation to set the URI of created resource in the response header in case of POST (“Location header”) is
explained here.
• The URI of created resource is set in Location header by REST API process.
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<MemberResource> postMembers(
@RequestBody @Validated({ PostMembers.class, Default.class })
MemberResource requestedResource,
// (1)
UriComponentsBuilder uriBuilder) {
// (2)
URI createdUri = uriBuilder.path("/members/{memberId}")
.buildAndExpand(responseResource.getMemberId()).toUri();
// (3)
return ResponseEntity.created(createdUri).body(responseResource);
}
// omitted
Method that builds an URI to be set in link information should be implemented as required.
• Response example
Following response header is obtained in the actual operation.
When conforming to HTTP specifications, it is necessary to return the list of HTTP methods that are allowed to
be called for each resource. Therefore, it is necessary to add the setting for dispatching OPTIONS method
request, to the Controller.
By DispatcherServlet default setting, the request for OPTIONS method is not dispatched in the Controller
with the list of methods allowed by DispatcherServlet being set in the Allow header.
• web.xml
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/spring/spring-mvc-rest.xml</param-value>
</init-param>
<!-- (1) -->
<init-param>
<param-name>dispatchOptionsRequest</param-name>
<param-value>true</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
When conforming to HTTP specifications, it is necessary to return the list of HTTP methods that are allowed to
be called for each resource.
API implementation that responds with list of HTTP methods (REST AP) supported by the resource specified in
URI, is shown below.
@RequestMapping("members")
@RestController
public class MembersRestController {
// omitted
// (1)
memberService.getMember(memberId);
// (2)
return ResponseEntity
.ok()
.allow(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.PUT,
HttpMethod.DELETE, HttpMethod.OPTIONS).build();
// omitted
(1) Call domain layer Service method and check to confirm if a resource matching with the ID
fetched from path variable exists.
(2) Set HTTP method supported by resource specified in URI, in Allow header.
• Request example
• Response example
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: 6d7bbc818c7f44e7942c54bc0ddc15bb
Allow: GET,HEAD,PUT,DELETE,OPTIONS
Content-Length: 0
Date: Mon, 17 Mar 2014 01:54:27 GMT
In order to conform to HTTP specifications, when GET method is implemented, HEAD method also needs to be
implemented.
API implementation that responds with meta-information of the resource specified in URI, is as follows:
@RequestMapping("members")
@RestController
public class MemberRestController {
// omitted
@RequestMapping(value = "{memberId}",
method = { RequestMethod.GET,
RequestMethod.HEAD }) // (1)
@ResponseStatus(HttpStatus.OK)
public MemberResource getMember(
@PathVariable("memberId") String memberId) {
// omitted
}
// omitted
• Request example
• Response example
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
X-Track: 71093a551e624c149867b6bfec486d2c
Content-Type: application/json;charset=UTF-8
Content-Length: 452
Date: Thu, 13 Mar 2014 13:25:23 GMT
Settings that prevent CSRF measures implementation for RESTful Web Service requests, are explained below.
Tip: Need to use session is eliminated when CSRF measures are not to be implemented.
In the following configuration example, session will not be used in Spring Security process.
CSRF measures are enabled as per the default settings of Blank project. By adding following settings, the process
for CSRF measures is prevented for RESTful Web Service requests.
• spring-security.xml
create-session="stateless">
<sec:http-basic />
<sec:csrf disabled="true"/>
</sec:http>
<sec:http>
<sec:access-denied-handler ref="accessDeniedHandler"/>
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER"/>
<sec:form-login/>
<sec:logout/>
<sec:session-management />
</sec:http>
When handling XML format data in RESTful Web Service, it is necessary to implement XXE (XML External
Entity) Injection measures.
terasoluna-gfw-web 1.0.1.RELEASE or higher versions are Spring MVC (3.2.10.RELEASE and above)
dependent. As these Spring MVC versions implement XXE Injection measures, it is not necessary to implement
them individually.
• pom.xml
(2) Spring version should be fetched from the placeholder (${org.springframework-version}) that
manages version number of Spring defined in pom.xml of terasoluna-gfw-parent.
Perform Bean definition for mutual conversion between XML and object by using the class provided by Spring-
oxm.
• spring-mvc-rest.xml
<mvc:annotation-driven>
<mvc:message-converters>
<!-- (3) -->
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xmlMarshaller" /> <!-- (4) -->
<property name="unmarshaller" ref="xmlMarshaller" /> <!-- (5) -->
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
(2) Specify package name with JAXB JavaBean (JavaBean assigned with
javax.xml.bind.annotation.XmlRootElement annotation) stored in
packagesToScan property.
JAXB JavaBean stored under the specified package is scanned and is registered as JavaBean for
marshalling and unmarshalling.
JavaBean is scanned by the mechanism same as base-package attribute of
<context:component-scan>.
• JodaDateTimeConverter.java
package org.terasoluna.examples.rest.infra.dozer.converter;
import org.dozer.DozerConverter;
import org.joda.time.DateTime;
public JodaDateTimeConverter() {
super(DateTime.class, DateTime.class);
}
@Override
public DateTime convertTo(DateTime source, DateTime destination) {
// This method not called, because type of from/to is same.
return convertFrom(source, destination);
}
@Override
public DateTime convertFrom(DateTime source, DateTime destination) {
return source;
}
• JodaLocalDateConverter.java
package org.terasoluna.examples.rest.infra.dozer.converter;
import org.dozer.DozerConverter;
import org.joda.time.LocalDate;
public JodaLocalDateConverter() {
super(LocalDate.class, LocalDate.class);
}
@Override
public LocalDate convertTo(LocalDate source, LocalDate destination) {
// This method not called, because type of from/to is same.
return convertFrom(source, destination);
}
@Override
public LocalDate convertFrom(LocalDate source, LocalDate destination) {
return source;
}
<configuration>
<custom-converters>
<!-- (2) -->
<converter type="org.terasoluna.examples.rest.infra.dozer.converter.JodaDateTimeC
<class-a>org.joda.time.DateTime</class-a>
<class-b>org.joda.time.DateTime</class-b>
</converter>
<converter type="org.terasoluna.examples.rest.infra.dozer.converter.JodaLocalDate
<class-a>org.joda.time.LocalDate</class-a>
<class-b>org.joda.time.LocalDate</class-b>
</converter>
</custom-converters>
</configuration>
</mappings>
(2) In the above example, custom converter definitions for Joda-Time classes
(org.joda.time.DateTime and org.joda.time.LocalDate) are added.
Note: When Dozer is used in domain layer as well, it is recommended to store the file defining Dozer
operation settings, in domain layer project (xxx-domain).
When Dozer is used only in application layer, it may be stored in application layer project (xxx-web).
Out of the application layer source codes used in explanation How to use, full version of the source code that was
pasted in fragments, is attached herewith.
(3) ApiGlobalExceptionHandler.java
• JavaBean
• Configuration file
MemberRestController.java
java/org/terasoluna/examples/rest/api/member/MemberRestController.java
package org.terasoluna.examples.rest.api.member;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.terasoluna.examples.rest.api.member.MemberResource.PostMembers;
import org.terasoluna.examples.rest.api.member.MemberResource.PutMember;
import org.terasoluna.examples.rest.domain.model.Member;
import org.terasoluna.examples.rest.domain.service.member.MemberService;
@RequestMapping("members")
@RestController
public class MemberRestController {
@Inject
MemberService memberService;
@Inject
Mapper beanMapper;
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public Page<MemberResource> getMembers(@Validated MembersSearchQuery query,
Pageable pageable) {
return responseResource;
}
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<MemberResource> getMembers() {
List<Member> members = memberService.findAll();
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public MemberResource postMembers(@RequestBody @Validated({
PostMembers.class, Default.class }) MemberResource requestedResource) {
return responseResource;
}
@ResponseStatus(HttpStatus.OK)
public MemberResource getMember(@PathVariable("memberId") String memberId) {
return responseResource;
}
@ResponseStatus(HttpStatus.OK)
public MemberResource putMember(
@PathVariable("memberId") String memberId,
@RequestBody @Validated({
PutMember.class, Default.class }) MemberResource requestedResource) {
return responseResource;
}
@ResponseStatus(HttpStatus.NO_CONTENT)
public void deleteMember(@PathVariable("memberId") String memberId) {
memberService.deleteMember(memberId);
ApiErrorCreator.java
java/org/terasoluna/examples/rest/api/common/error/ApiErrorCreator.java
package org.terasoluna.examples.rest.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.WebRequest;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
@Component
public class ApiErrorCreator {
@Inject
MessageSource messageSource;
}
return apiError;
}
ApiGlobalExceptionHandler.java
java/org/terasoluna/examples/rest/api/common/error/ApiGlobalExceptionHandler.java
package org.terasoluna.examples.rest.api.common.error;
import javax.inject.Inject;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ExceptionCodeResolver;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.exception.ResultMessagesNotificationException;
@ControllerAdvice
public class ApiGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
ApiErrorCreator apiErrorCreator;
@Inject
ExceptionCodeResolver exceptionCodeResolver;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleBindingResult(ex, ex.getBindingResult(), headers, status,
request);
}
@Override
protected ResponseEntity<Object> handleBindException(BindException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleBindingResult(ex, ex.getBindingResult(), headers, status,
request);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
if (ex.getCause() instanceof Exception) {
return handleExceptionInternal((Exception) ex.getCause(), null,
headers, status, request);
} else {
return handleExceptionInternal(ex, null, headers, status, request);
}
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.NOT_FOUND, request);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex,
WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
@ExceptionHandler({ OptimisticLockingFailureException.class,
PessimisticLockingFailureException.class })
public ResponseEntity<Object> handleLockingFailureException(Exception ex,
WebRequest request) {
return handleExceptionInternal(ex, null, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleSystemError(Exception ex,
WebRequest request) {
return handleExceptionInternal(ex, null, new HttpHeaders(),
HttpStatus.INTERNAL_SERVER_ERROR, request);
}
Source code of the domain layer class created at the time of REST API implementation
Source code of domain layer class called from REST API explained in How to use is attached herewith.
Also, infrastructure layer is implemented by using MyBatis3.
(2) MemberCredentia.java
(3) Gender.java
(6) MemberServiceImpl.java
(8) GenderTypeHandler.java
(9) member-mapping.xml
(10) mybatis-config.xml
(11) MemberRepository.xml
Member.java
java/org/terasoluna/examples/rest/domain/model/Member.java
package org.terasoluna.examples.rest.domain.model;
import java.io.Serializable;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
MemberCredentia.java
java/org/terasoluna/examples/rest/domain/model/MemberCredential.java
package org.terasoluna.examples.rest.domain.model;
import java.io.Serializable;
import org.joda.time.DateTime;
this.version = version;
}
Gender.java
java/org/terasoluna/examples/rest/domain/model/Gender.java
package org.terasoluna.examples.rest.domain.model;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.util.Assert;
static {
Map<String, Gender> map = new HashMap<>();
for (Gender gender : values()) {
map.put(gender.code, gender);
}
genderMap = Collections.unmodifiableMap(map);
}
MemberRepository.java
java/org/terasoluna/examples/rest/domain/repository/member/MemberRepository.java
package org.terasoluna.examples.rest.domain.repository.member;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
import org.terasoluna.examples.rest.domain.model.Member;
List<Member> findAll();
MemberService.java
java/org/terasoluna/examples/rest/domain/service/member/MemberService.java
package org.terasoluna.examples.rest.domain.service.member;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.terasoluna.examples.rest.domain.model.Member;
List<Member> findAll();
MemberServiceImpl.java
java/org/terasoluna/examples/rest/domain/service/member/MemberServiceImpl.java
package org.terasoluna.examples.rest.domain.service.member;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.apache.ibatis.session.RowBounds;
import org.dozer.Mapper;
import org.joda.time.DateTime;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.terasoluna.examples.rest.domain.message.DomainMessageCodes;
import org.terasoluna.examples.rest.domain.model.Member;
import org.terasoluna.examples.rest.domain.model.MemberCredential;
import org.terasoluna.examples.rest.domain.repository.member.MemberRepository;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessages;
@Transactional
@Service
public class MemberServiceImpl implements MemberService {
@Inject
MemberRepository memberRepository;
@Inject
JodaTimeDateFactory dateFactory;
@Inject
PasswordEncoder passwordEncoder;
@Inject
Mapper beanMapper;
@Override
@Transactional(readOnly = true)
public List<RestMember> findAll() {
return restMemberRepository.findAll();
}
@Override
@Transactional(readOnly = true)
public Page<Member> searchMembers(String name, Pageable pageable) {
List<Member> members = null;
// Count Members by search criteria
long total = memberRepository.countByContainsName(name);
if (0 < total) {
RowBounds rowBounds = new RowBounds(pageable.getOffset(), pageable.getPageSize());
members = memberRepository.findPageByContainsName(name, rowBounds);
} else {
members = new ArrayList<Member>();
}
return new PageImpl<Member>(members, pageable, total);
}
@Override
@Transactional(readOnly = true)
public Member getMember(String memberId) {
// find member
Member member = memberRepository.findOne(memberId);
if (member == null) {
@Override
public Member createMember(Member creatingMember) {
MemberCredential creatingCredential = creatingMember
.getCredential();
creatingMember.setCreatedAt(currentDateTime);
creatingMember.setLastModifiedAt(currentDateTime);
// encrypt password
String rawPassword = creatingCredential.getPassword();
creatingCredential.setPassword(passwordEncoder.encode(rawPassword));
creatingCredential.setPasswordLastChangedAt(currentDateTime);
creatingCredential.setLastModifiedAt(currentDateTime);
@Override
public Member updateMember(String memberId, Member updatingMember) {
// get member
Member member = getMember(memberId);
@Override
public void deleteMember(String memberId) {
DomainMessageCodes.java
java/org/terasoluna/examples/rest/domain/message/DomainMessageCodes.java
package org.terasoluna.examples.rest.domain.message;
/**
* Message codes of domain layer message.
* @author DomainMessageCodesGenerator
*/
public class DomainMessageCodes {
private DomainMessageCodes() {
// NOP
}
GenderTypeHandler.java
java/org/terasoluna/examples/infra/mybatis/typehandler/GenderTypeHandler.java
package org.terasoluna.examples.infra.mybatis.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.terasoluna.examples.domain.model.Gender;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.BaseTypeHandler;
@Override
public Gender getNullableResult(ResultSet rs, String columnName) throws SQLException {
return getByCode(rs.getString(columnName));
}
@Override
public Gender getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return getByCode(rs.getString(columnIndex));
}
@Override
public Gender getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return getByCode(cs.getString(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Gender parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter.getCode());
}
member-mapping.xml
In the implemented Service class, “Bean Mapping (Dozer)” is used while copying the value specified by client in
Member object.
When it is alright to simply copy the field values, Bean mapping definition need not be added. However, in this
implementation it needs to be ensured that, items which are not to be updated (memberId, credential,
createdAt and version) are not copied.
Bean mapping definition needs to be added in order to ensure that specific fields are not copied.
resources/META-INF/dozer/member-mapping.xml
<mapping map-id="member.update">
<class-a>org.terasoluna.examples.rest.domain.model.Member</class-a>
<class-b>org.terasoluna.examples.rest.domain.model.Member</class-b>
<field-exclude>
<a>memberId</a>
<b>memberId</b>
</field-exclude>
<field-exclude>
<a>credential</a>
<b>credential</b>
</field-exclude>
<field-exclude>
<a>createdAt</a>
<b>createdAt</b>
</field-exclude>
<field-exclude>
<a>lastModifiedAt</a>
<b>lastModifiedAt</b>
</field-exclude>
<field-exclude>
<a>version</a>
<b>version</b>
</field-exclude>
</mapping>
</mappings>
mybatis-config.xml
MyBatis3 operations can be customized by adding a configuration values in MyBatis configuration file.
MyBatis3 does not support Joda-time classes (org.joda.time.DateTime, org.joda.time.LocalDateTime,
org.joda.time.LocalDate etc.).
Hence, when Joda-Time class is used in the field of Entity class, it is necessary to provide a TypeHandler for
Joda-Time.
TypeHandler implementation for mapping the org.joda.time.DateTime with java.sql.Timestamp is done by
referring [Implementing TypeHandler for Joda-Time].
resources/META-INF/mybatis/mybatis-config.xml
<settings>
<setting name="jdbcTypeForNull" value="NULL" />
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<typeAliases>
<package name="org.terasoluna.examples.infra.mybatis.typehandler" />
</typeAliases>
<typeHandlers>
<package name="org.terasoluna.examples.infra.mybatis.typehandler" />
</typeHandlers>
</configuration>
MemberRepository.xml
resources/org/terasoluna/examples/rest/domain/repository/member/MemberRepository.xml
<sql id="selectMember">
SELECT
member.member_id as member_id
,member.first_name as first_name
,member.last_name as last_name
,member.gender as gender
,member.date_of_birth as date_of_birth
,member.email_address as email_address
,member.telephone_number as telephone_number
,member.zip_code as zip_code
,member.address as address
,member.created_at as created_at
,member.last_modified_at as last_modified_at
,member.version as version
,credential.sign_id as sign_id
,credential.password as password
,credential.previous_password as previous_password
,credential.password_last_changed_at as password_last_changed_at
,credential.last_modified_at as credential_last_modified_at
,credential.version as credential_version
FROM
t_member member
INNER JOIN t_member_credential credential ON credential.member_id = member.member_id
</sql>
<sql id="whereMember">
WHERE
member.first_name LIKE #{nameContainingCondition} ESCAPE '~'
OR member.last_name LIKE #{nameContainingCondition} ESCAPE '~'
</sql>
(
#{memberId}
,#{credential.signId}
,#{credential.password}
,#{credential.previousPassword}
,#{credential.passwordLastChangedAt}
,#{credential.lastModifiedAt}
,1
)
</insert>
</mapper>
5.2.1 Overview
This chapter explains how to call RESTful Web Service(REST API) by using
org.springframework.web.client.RestTemplate offered by Spring Framework.
What is RestTemplate
RestTemplate is a class which offers a method for calling REST API(Web API) and is a HTTP client offered
by Spring Framework.
The method by which RestTemplate access REST API (Web API) is explained before explaining basic imple-
mentation method.
(1) Application Call RestTemplate method and request to call REST API (Web
API).
(4) Configure message (JSON etc) in the request body and carry out
ClientHttpRequest
request in REST API (Web API) through HTTP.
(5) RestTemplate Determine errors and perform error handling for HTTP
transmission using ResponseErrorHandler.
(8) Return results (Java object) of calling REST API (Web API) to the
application.
When response received from REST API is to be processed in another thread (asynchronous pro-
cessing), org.springframework.web.client.AsyncRestTemplate should be used instead of
HttpMessageConverter
(1) A class for conversion of “HTTP body (text or binary data) ⇔ byte[]
org.springframework.http.converter.
Byte array”.
It supports all media types (*/*) by default.
ByteArrayHttpMessageConverter
StringHttpMessageConverter
When media type is multipart/form-data, conversion of “HTTP body from MultiValueMap object”
can be done, however, conversion “from HTTP body to MultiValueMap object” is currently not supported.
Hence, an independent implementation is required if conversion “from HTTP body to MultiValueMap object”
is to be carried out.
Table.5.2 HttpMessageConverter that is registered when a dependent library exists on the class path
(6) A class for conversion of “HTTP body (Atom) ⇔ Atom feed Feed *4
org.springframework.http.converter.feed.
object”.
It supports media type for ATOM
AtomFeedHttpMessageConverter
(application/atom+xml) by default.
(It is registered when ROME exists on the class path)
(7) A class for conversion of “HTTP body (RSS) ⇔ Rss channel Channel *5
org.springframework.http.converter.feed.
object”.
It supports media type for RSS (application/rss+xml)
RssChannelHttpMessageConverter
by default.
(It is registered when ROME exists on the class path)
(application/json,application/*+json) by Map
MappingJackson2HttpMessageConverter
default.
(It is registered when Jackson2 exists on the class path)
(text/xml,application/xml,application/*-xml) Map
MappingJackson2XmlHttpMessageConverter
by default.
(It is registered when Jackson-dataformat-xml exists on the
class path)
(text/xml,application/xml,application/*-xml)
Jaxb2RootElementHttpMessageConverter
by default.
(It is registered when JAXB exists on the class path)
1024 5 Web Services
ClientHttpRequestFactory
RestTemplate delegates the process of communicating with the server to implementation class of three inter-
faces given below.
• org.springframework.http.client.ClientHttpRequestFactory
• org.springframework.http.client.ClientHttpRequest
• org.springframework.http.client.ClientHttpResponse
Note that, the main implementation class of ClientHttpRequestFactory offered by Spring Framework is
as given below.
Netty4ClientHttpRequestFactory
OkHttpClientHttpRequestFactory
ResponseErrorHandler
RestTemplate handles the errors during the communication with the server by delegating to
org.springframework.web.client.ResponseErrorHandler interface.
DefaultResponseErrorHandler carries out error handling as below according to values of HTTP status
codes which have been sent as a response from the server.
• When response code is standard (2xx), error handling is not carried out.
Response data at the time of error (HTTP status code, response header, response body etc) can be fetched by
calling getter method of exception class.
ClientHttpRequestInterceptor
ClientHttpRequestInterceptor can be used for multiple times and is executed as a chain in a speci-
fied sequence. This operation is similar to working of a servlet filter and the HTTP communication process by
ClientHttpRequest is registered as a chain destination executed at the end. For example, when you want to
cancel communication with the server once it fulfils a certain condition, a chain destination need not be called.
This chapter explains how to implement a client process which uses RestTemplate.
In this guideline, only the implementation example of client process which use GET method and POST method
is introduced, however, RestTemplate supports other HTTP methods (PUT, PATCH, DELETE, HEAD, OP-
TIONS etc) as well and can be used in the similar way. Refer Javadoc of RestTemplate for details.
RestTemplate Setup
When RestTemplate is used, RestTemplate is registered in DI container and injected in the component
which uses RestTemplate.
<dependencies>
</dependencies>
(1) When RestTemplate is used similar to default configuration, register a bean by using default
constructor.
<bean id="clientHttpRequestFactory"
class="org.springframework.http.client.SimpleClientHttpRequestFactory"> <!-- (1) -->
<!-- Set properties for customize a http communication (omit on this sample) -->
</bean>
Also, refer
Using RestTemplate
@Service
public class AccountServiceImpl implements AccountService {
@Inject
RestTemplate restTemplate;
// ...
When only the response body is required to be fetched, getForObject method is used.
@Value("${api.url:http://localhost:8080/api}")
URI uri;
Internal method
(1) When getForObject method is used, the response body value is sent as a return value.
Response body data is returned after it has been converted to Java class specified in the second
argument, by using HttpMessageConverter.
When HTTP status code, response header and response body must be fetched, getForEntity method is used.
ResponseEntity<User> responseEntity =
restTemplate.getForEntity(uri, User.class); // (1)
HttpStatus statusCode = responseEntity.getStatusCode(); // (2)
HttpHeaders header = responseEntity.getHeaders(); // (3)
User user = responseEntity.getBody(); // (4)
Note: ResponseEntity
ResponseEntity is a class which shows HTTP response and can fetch HTTP status code, response header and
response body information. Refer Javadoc of ResponseEntity for details.
import part
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
@Value("${api.url:http://localhost:8080/api}")
URI uri;
Internal method
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);//(3)
(1) Use get method of RequestEntity and generate request builder for GET request.
Specify URI in the parameter.
(3) Use exchange method and send request. Specify response data type in the second argument.
ResponseEntity<T> is sent as a response. Specify response data type in Type parameter.
Note: RequestEntity
RequestEntity is a class which shows HTTP request and can set connection URI, HTTP method, request
header and request body. Refer Javadoc of RequestEntity for details.
Also, refer Configuration of request header for how to configure a request header.
• When a detailed setting like setting any header is to be carried out, RequestEntity and exchange
methods are used.
When only response body is required to be fetched as POST results, postForObject method is used.
//...
When HTTP status code, response header and response body are to be fetched as POST results,
postForEntity method is used.
//...
ResponseEntity<User> responseEntity =
restTemplate.postForEntity(uri, user, User.class); // (1)
(1) postForEntity method can implement a POST request easily similar to getForObject
method.
When postForEntity method is used, ResponseEntity is sent as a return value.
Fetch response body value from ResponseEntity.
When a request header is to be specified, RequestEntity is generated and exchange method is used.
import part
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
@Value("${api.url:http://localhost:8080/api}")
URI uri;
Internal method
//...
ResponseEntity<User> responseEntity =
restTemplate.exchange(requestEntity, User.class);//(4)
(1) Use RequestEntity and generate a request. Specify type of the data specified in the request body,
in Type parameter.
(2) Use post method and generate a request builder for POST request. Specify URI in the parameter.
When the message of response body received from server as a response is in collection format, the implementation
is as below.
If RequestEntity and exchange methods are used, a specific header or any other header can be set by using
RequestEntity method. Refer Javadoc of RequestEntity for details.
//...
When the format of data to be fetched from server is specified, Accept header must be specified. When the server
does not support multiple data format responses, Accept header may not be specified explicitly.
//...
//...
(1) Use header method of RequestEntity.HeadersBuilder and specify name and value of
request header.
In the implementation example above, credentials information necessary for Basic authentication is
specified in Authorization header.
Error Handling
Exceptions like
Note: An example of exception handling when a server error has occurred is shown below as an implementation
example.
@Value("${api.retry.maxCount}")
int retryMaxCount;
@Value("${api.retry.retryWaitTimeCoefficient}")
int retryWaitTimeCoefficient;
Internal method
int retryCount = 0;
while (true) {
try {
if (log.isInfoEnabled()) {
log.info("Success({}) ", responseEntity.getStatusCode());
}
break;
if (retryCount == retryMaxCount) {
throw e;
}
retryCount++;
if (log.isWarnEnabled()) {
log.warn("An error ({}) occurred on the server. (The number of retries:{} Times)", e.
retryCount);
}
try {
Thread.sleep(retryWaitTimeCoefficient * retryCount);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
//...
}
(1) Catch exception and perform error handling. In case of a server error (500 system), catch
HttpServerErrorException.
In the example below, the error handler is extended so as to return ResponseEntity even when a server error
and a client error has occurred.
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
@Override
public void handleError(ClientHttpResponse response) throws IOException {
//Don't throw Exception.
}
int retryCount = 0;
while (true) {
break;
if (retryCount == retryMaxCount) {
break;
}
retryCount++;
if (log.isWarnEnabled()) {
log.warn("An error ({}) occurred on the server. (The number of retries:{} Times)",
responseEntity.getStatusCode(), retryCount);
}
try {
Thread.sleep(retryWaitTimeCoefficient * retryCount);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
//...
}
}
(1) In the implementation example above, since error handler is extended so as to return
ResponseEntity even at the time of error, it is necessary to check whether process results are
normal after fetching HTTP status code from ResponseEntity thus returned.
(2) HTTP status code can be fetched from returned ResponseEntity even at the time of error and
process can be controlled corresponding to that value.
When a timeout period is to be specified for communicating with server, define a bean as given below.
<bean id="clientHttpRequestFactory"
class="org.springframework.http.client.SimpleClientHttpRequestFactory">
<property name="connectTimeout" value="${api.connectTimeout: 2000}" /><!-- (1) -->
<property name="readTimeout" value="${api.readTimeout: 2000}" /><!-- (2) -->
</bean>
ResourceAccessException wraps the cause exception. Cause exception during connection timeout and
read timeout occurrence is java.net.SocketTimeoutException for both. When default implementation
(SimpleClientHttpRequestFactory) is used, it must be added that type of timeout occurrence cannot be
distinguished by the type of exception class.
Note that, since operation while using HttpRequestFactory is not verified, cause exception is likely to be
different from the one described above. When other HttpRequestFactory is used, appropriate exception
handling must be employed after assessing the exception occurred during the timeout.
Implementation is as given below when a SSL self-signed certificate is to be used in the test environment.
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
@Override
public ClientHttpRequestFactory getObject() throws Exception {
// (1)
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(this.getClass().getClassLoader()
.getResourceAsStream(this.keyStoreFileName),
this.keyStorePassword);
// (2)
HttpClient httpClient = HttpClientBuilder.create()
.setSSLContext(sslContext).build();
// (3)
ClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
httpClient);
return factory;
}
@Override
public Class<?> getObjectType() {
return ClientHttpRequestFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
(1) Create SSL context based on file name and password of keystore file which is specified in subsequent
bean definition.
Keystore file of SSL self-signed certificate to be used is placed on the class path.
• pom.xml
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
Define RestTemplatewhich carries out SSL communication using SSL self-signed certificate.
The method to use RestTemplate is same as the method when SSL self-signed certificate is not used.
Basic authentication
@Value("${api.auth.userid}")
String userid;
@Value("${api.auth.password}")
String password;
Internal method
Note: java.util.Base64 of Java standard is used for Java SE8 and later versions. Earlier,
org.springframework.security.crypto.codec.Base64of Spring Security is used.
(2) Specify parameter name in key and add file to be uploaded in MultiValueMap.
In the example above, file placed on the class path is added as an uploaded file by specifying
parameter name as file.
(4) Specify MultiValueMap in the request body wherein the uploaded file has been stored.
• org.springframework.core.io.PathResource
• org.springframework.core.io.FileSystemResource
• org.springframework.core.io.ClassPathResource
• org.springframework.core.io.UrlResource
File download
ResponseEntity<byte[]> responseEntity =
restTemplate.exchange(requestEntity, byte[].class);//(1)
(1) Handle downloaded file with a specified data type. Here, byte array is specified.
// (1)
final ResponseExtractor<ResponseEntity<File>> responseExtractor =
new ResponseExtractor<ResponseEntity<File>>() {
// (2)
@Override
public ResponseEntity<File> extractData(ClientHttpResponse response)
throws IOException {
return ResponseEntity.status(response.getStatusCode())
.headers(response.getHeaders()).body(rcvFile);
}
};
// (3)
ResponseEntity<File> responseEntity = this.restTemplate.execute(targetUri,
HttpMethod.GET, null, responseExtractor);
if (HttpStatus.OK.equals(responseEntity.getStatusCode())) {
File getFile = responseEntity.getBody();
.....
(1) Create a process to create a return value of RestTemplate#execute,from the response fetched
from RestTemplate#execute.
(2) Read data from response body (InputStream) and create a file.
Created file, HTTP header and status code are stored in ResponseEntity<File> and returned.
Implementation example of file download (when file size is large (example wherein ResponseEntity is not
used))
When status code determination and HTTP header reference are not required, File should be returned instead of
ResponseEntity as given below.
@Override
public File extractData(ClientHttpResponse response)
throws IOException {
return rcvFile;
}
};
Implementation can be carried out by using URI template for handling RESTful URL.
@Value("${api.serverUrl}/api/users/{userId}") // (1)
String uriStr;
Internal method
(1) Variable {userId} of URI template is changed to value specified while using RestTeamplate.
(2) One variable of URI template is replaced with a value specified in third argument of
getForObject method and processed as “http://localhost:8080/api/users/0001”.
@Value("${api.serverUrl}/api/users/{action}") // (1)
String uriStr;
Internal method
//...
(1) Variable {action} of URI template is changed to value specified while using RestTeamplate.
(2) By using UriComponentsBuilder, first variable of URI template is replaced by value specified
in the argument of buildAndExpand and “http://localhost:8080/api/users/create” URI is created.
Refer Javadoc of UriComponentsBuilder for details.
If the requirements of message conversion are not met by HttpMessageConverter registered as default, an
arbitrary HttpMessageConverter can be registered. However, since HttpMessageConverter regis-
tered as default is deleted, the required HttpMessageConverter should all be individually registered.
<bean id="jaxb2CollectionHttpMessageConverter"
class="org.springframework.http.converter.xml.Jaxb2CollectionHttpMessageConverter" /> <!-- (
• Logging process
is introduced below.
Logging process
package com.example.restclient;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
if (log.isInfoEnabled()) {
String requestBody = new String(body, StandardCharsets.UTF_8);
if (log.isInfoEnabled()) {
log.info("Response Header {}", response.getHeaders()); // (4)
log.info("Response Status Code {}", response.getStatusCode()); // (5)
}
(4) Implement a common process which is to be carried out after receiving a response.
In the implementation example above, response header details are output in a log.
When it is necessary to configure a request header for Basic authentication to access the server, implementation is
as given below.
package com.example.restclient;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
@Value("${api.auth.userid}")
String userid;
@Value("${api.auth.password}")
String password;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
return response;
}
Applying ClientHttpRequestInterceptor
Asynchronous request
(1) When AsyncRestTemplate is to be used as per default setup, register a bean by using a default
constructor.
In case of default configuration, SimpleClientHttpRequestFactory which has set
org.springframework.core.task.SimpleAsyncTaskExecutor is set as
org.springframework.core.task.AsyncListenableTaskExecutor in
org.springframework.http.client.AsyncClientHttpRequestFactory of
AsyncRestTemplate.
SimpleAsyncTaskExecutor set as default generates threads without using a thread pool and there is no re-
striction on number of concurrent execution of threads. Hence, when the number of threads to be used concurrently
is very large, OutOfMemoryError is likely to occur.
This guideline introduces an implementation example to customise the task execution process only, however HTTP
communication process can also be customised for AsyncRestTemplate. Refer Javadoc of AsyncRestTem-
plate for details.
Also, customisation for other than thread pool size is possible for ThreadPoolTaskExecutoras well. Refer
Javadoc of ThreadPoolTaskExecutor for details.
@Inject
AsyncRestTemplate asyncRestTemplate;
Internal method
ListenableFuture<ResponseEntity<User>> responseEntity =
asyncRestTemplate.getForEntity(uri, User.class); // (1)
@Override
public void onFailure(Throwable t) {
//...
}
});
5.2.4 Appendix
When the server is to be accessed via HTTP Proxy server, HTTP Proxy server must be configured in system
property or JVM starting argument, or Bean definition of RestTemplate. When the server is configured in
system property or JVM starting argument, it impacts the overall application. Hence, an example wherein HTTP
Proxy server is configured for each RestTemplate is introduced.
HTTP Proxy server for each RestTemplate can be configured for SimpleClientHttpRequestFactory
which is a default implementation of ClientHttpRequestFactory interface. How-
ever, since credentials cannot be configured in SimpleClientHttpRequestFactory,
HttpComponentsClientHttpRequestFactory is used while authenticating Proxy.
HttpComponentsClientHttpRequestFactory is an implementation class of
ClientHttpRequestFactory interface which generates a request using Apache HttpComponents
HttpClient.
Connection destination of HTTP Proxy server for which credentials are essential is specified for RestTemplate
by using HttpComponentsClientHttpRequestFactory.
pom.xml
(1) Add Apache HttpComponents Client to dependent library of pom.xml in order to use
Apache HTTP Client which is used in HttpComponentsClientHttpRequestFactory.
Note that, since Apache HttpComponents Client version is managed in Spring IO Platform ,
it is not necessary to define Apache HttpComponents Client version here.
<constructor-arg>
<bean factory-bean="proxyHttpClientBuilder" factory-method="build" />
</constructor-arg>
</bean>
</constructor-arg>
</bean>
(2) Configure org.apache.http.HttpHost which performs HTTP Proxy server setting, in proxy
property of HttpClientBuilder.
(3) Set value of key rscl.http.proxyHost configured in property file in the first argument of
HttpHost constructor, as a host name of HTTP Proxy server.
(4) Set value of key rscl.http.proxyPort configured in property file in the second argument of
HttpHost constructor, as a port number of HTTP Proxy server.
When credentials (user name and password) are required for accessing HTTP Proxy server, credentials are set by
using org.apache.http.impl.client.BasicCredentialsProvider.
FactoryBean class
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Value;
// (1)
public class BasicCredentialsProviderFactoryBean implements FactoryBean<BasicCredentialsProvider>
// (2)
@Value("${rscl.http.proxyHost}")
String host;
// (3)
@Value("${rscl.http.proxyPort}")
int port;
// (4)
@Value("${rscl.http.proxyUserName}")
String userName;
// (5)
@Value("${rscl.http.proxyPassword}")
String password;
@Override
public BasicCredentialsProvider getObject() throws Exception {
// (6)
AuthScope authScope = new AuthScope(this.host, this.port);
// (7)
UsernamePasswordCredentials usernamePasswordCredentials =
new UsernamePasswordCredentials(this.userName, this.password);
// (8)
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(authScope, usernamePasswordCredentials);
return credentialsProvider;
}
@Override
public Class<?> getObjectType() {
return BasicCredentialsProvider.class;
@Override
public boolean isSingleton() {
return true;
}
}
(2) Set value of key rscl.http.proxyHost set in property file in instance variable, as a host name
of HTTP Proxy server.
(3) Set value of key rscl.http.proxyPort set in property file in instance variable, as a port number
of HTTP Proxy server.
(4) Set value of key rscl.http.proxyUserName set in property file in instance variable, as a user
name of HTTP Proxy server.
(5) Set value of key rscl.http.proxyPassword set in property file in instance variable, as a
password of HTTP Proxy server.
For configuration while using JSR-310 Date and Time API as a property of JavaBean which represents a resource
(Resource class), refer “Configuration while using JSR-310 Date and Time API / Joda Time ”.
5.3.1 Overview
This chapter explains fundamental concepts of SOAP Web Service and development of both SOAP server and
client which use JAX-WS.
• “How to use“
It explains application configuration and how to implement API for SOAP Web Service which use
JAX-WS.
SOAP
How to use SOAP Web Service with the configuration given in following figure is explained in this guideline.
However, SOAP Web Service can also be used with a configuration different from the configuration given below.
(Example: when a client is a batch etc)
(1) A Web application which communicates with another SOAP server is assumed as a client.
Although it is referred as a client, precautions must be taken since it is envisaged as a Web application.
(2) SOAP server publishes a Web service and performs a process by receiving XML through SOAP Web
Service from client. Operations like accessing database etc and performing business process are
assumed.
JAX-WS
JAX-WS is an abbreviation of “Java API for XML-Based Web Services” and is a Java standard API for handling
Web service using SOAP etc.
Using JAX-WS, Java object can be sent by converting the same to XML in conformance with SOAP
specifications.
Therefore, although information is exchanged in SOAP Web Service using XML, the user can handle the data
without being aware of XML structure.
Main Java EE servers like Oracle WebLogic Server or JBoss Enterprise Application Platform use JAX-WS
implementation on server side and can easily publish Web service by using the function without adding a specific
library.
However, since Tomcat does not implement JAX-WS, a separate JAX-WS implementation library must be added
while using Tomcat.
For details, refer “Web service development on Tomcat”.
Spring Framework supports JAX-WS linkage function and implementation can be easily done for both SOAP
server and client using this function.
Overview of the recommended access flow using this function is given below. Here, it is assumed that the Web
application acting as a SOAP client (Fig. on the left) access SOAP server (fig. on the right).
(2) [Client] Service calls WebService interface offered by SOAP server side.
In the Fig., Service calls the WebService interface, however, WebService interface can also be called
directly from Controller if required.
Note: Strictly speaking, SOAP server and client communicate using XML. Although Domain Ob-
ject and XML are mutually converted using JAXB during sending and receiving, SOAP Web Service
creator can carry out development without being aware of XML.
(5) [Server] If WebService interface gets called, WebService implementation class is called as an entity.
A WebService implementation class is provided as an implementation class of WebService interface
in SOAP server.
The WebService implementation class can inject the Bean of Spring DI container by @Inject etc.,
by inheriting
org.springframework.web.context.support.SpringBeanAutowiringSupport.
(6) [Server] Call Service for carrying out business process in WebService implementation class.
5.3.
(7) SOAP Web Service
[Server] Run(Server/Client)
business process by using Repository etc in Service. 1067
Note: Although a document driven Spring Web Service which develops a Web service is provided in the Spring,
it is not addressed in this guideline. For details, refer Spring Web Services.
Note: For details of JAX-WS implementation in Spring, refer Spring Framework Reference Documentation
-Remoting and web services using Spring(Web services)-.
It is recommended to develop Web service in TERASOLUNA Server Framework for Java (5.x) using JAX-WS
implementation of AP server and Spring function.
SOAP Web Service can be implemented for SOAP server or client by deploying a WAR file created by a web
project in the blank project, to AP server, similar to a normal Web application.
When Web service using JAX-WS is to be created, it is recommended to separately add two projects given below
besides the existing blank project.
• model project
• webservice project
model project stores Domain Object used in argument and return value of Web service.
webservice project stores an interface which calls the Web service.
Both the projects store only the classes that must be distributed from SOAP server to client.
Although client is again assumed to be a Web application, the basic idea for calling from desktop application or
command line interface remains the same.
(1) Add model project and webservice project offered by SOAP server to a conventional multi-project
while creating a client.
Here, it is assumed that both the server and the client are developed together.
The project details are explained in “How to create SOAP server”.
Refer “Project configuration is changed for SOAP server.” for how to add.
When server and client are not developed separately, and model project and webservice project are
not provided, or SOAP server is created in other than Java, Domain Object of model project and Web
service interface in webservice project must be created on their own.
Domain Object and Web service interface can be easily created from WSDL by using wsimport.
For details, refer “wsimport”.
(2) Add a model project and webservice project besides a conventional multi-project while creating a
SOAP server.
Publish these two projects to the client.
It is assumed that the model and webservice projects of the client are added in the Maven
dependencies.
If SOAP Web Service is created, definition of Web service interface called WSDL(Web Services Description
Language) is published and the client then implements SOAP Web Service based on the definition.
For WSDL details, refer W3C -Web Services Description Language (WSDL)-.
Access URL, method name, argument and return value for Web service implementation are defined in WSDL.
If SOAP Web Service is created as per this guideline, WSDL is published under following URL.
The URL must be specified at the client side.
Note: In the guideline, it is assumed that a WAR file is used for deploying a web project of multi-project
configuration in AP server. In this case, [server projectName]-web basically acts as a context route. However, it
must be noted that route changes depending on AP server.
Note: In this guideline, since it is assumed that SOAP server and client are together published as a Web applica-
tion, WSDL URL is specified in the client. The client can also be created by providing WSDL as a file instead of
Warning: In this guideline, AP server (library to be used in case of Tomcat) is configured to change mapping
of context route and access by following URL.
• http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-web/ws/TodoWebService?wsdl
How to map Web service in URL which is not under context route differs according to AP server. Refer
following for details.
Sr. No. AP server name Description
Project configuration
model project and webservice project are added as described in “Development of Web service using JAX-WS”.
Refer “Project configuration is changed for SOAP server.” for how to add.
(2) domain project Deploy Service which is called from WebService implementation
class.
Repository etc are same as used in the conventional project.
(4) model project Deploy only the class that is used in SOAP Web Service from the
classes that belong to the domain layer.
Input value and return results from the client use the class in the
project.
Application configuration
When Tomcat is to be used as AP server, “Web service development on Tomcat” must be implemented.
Besides, since the method to publish Web service is different according to AP server, refer manual of each AP
server for details.
Note: AP server manual is explained below as the reference material. It must be always checked that appropriate
version of the manual is being referred.
Oracle WebLogic Server 12.2.1: Oracle(R) Fusion Middleware Understanding WebLogic Web Services for Oracle
WebLogic Server Features and Standards Supported by WebLogic Web Services
JBoss Enterprise Application Platform 6.4: DEVELOPMENT GUIDE JAX-WS WEB SERVICES
[server projectName]-ws.xml is created for scanning the component to be used by Web service, com-
ponent scan is defined and injection in the Web service is enabled.
(1) Specify a package wherein the component to be used in Web service is stored.
[server projectName]-web/src/main/webapp/WEB-INF/web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<!-- Root ApplicationContext -->
<!-- (1) -->
<param-value>
classpath*:META-INF/spring/applicationContext.xml
classpath*:META-INF/spring/spring-security.xml
classpath*:META-INF/spring/[server projectName]-ws.xml
</param-value>
</context-param>
[server projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator" />
</bean>
Domain Object used in the argument and return value of Web service is created in model project.
It is not specifically different from general JavaBean which implements java.io.Serializable interface.
[server projectName]-model/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;
import java.io.Serializable;
import java.util.Date;
[server projectName]-webservice/src/main/java/com/example/ws/todo/TodoWebService.java
package com.example.ws.todo;
import java.util.List;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;
@WebMethod // (2)
@WebResult(name = "todo") // (3)
Todo getTodo(@WebParam(name = "todoId") /* (4) */ String todoId) throws WebFaultException;
Note: Value of targetNamespace attribute is defined in WSDL. Namespace of the Web service
is determined and is used for unique identification.
(2) Apply @WebMethod to the method which is published as a Web service method.
Method can be published on WSDL and used externally by applying this annotation.
(3) Apply @WebResult to return value and specify name in name attribute. It is not required in the
absence of a return value.
It is published as a return value on WSDL by applying this annotation.
(4) Apply @WebParam in the argument and specify name in name attribute.
Argument is published on WSDL and defined as a parameter required for external calling, by
applying this annotation.
For details of WebFaultException, refer “Implementing exception handling”.
When com.example is used as a domain and todo is used as an application name, Namespace is linked with Java
package as below.
Although it is not specified, naming of Namespace and package is summarised in XML Namespace Map-
ping(Red Hat JBoss Fuse).
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
package com.example.ws.todo;
import java.util.List;
import javax.inject.Inject;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.SOAPBinding;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import com.example.domain.model.Todo;
import com.example.domain.service.TodoService;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.exception.WsExceptionHandler;
import com.example.ws.todo.TodoWebService;
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
endpointInterface = "com.example.ws.todo.TodoWebService") // (1)
@BindingType(SOAPBinding.SOAP12HTTP_BINDING) // (2)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService { //
@Inject // (4)
TodoService todoService;
@Override // (5)
public Todo getTodo(String todoId) throws WebFaultException {
return todoService.getTodo(todoId);
}
Note: It is recommended to summarise Web service related class under ws package. This is to differentiate it
from application layer class which is placed under app package.
A method validation provided by Spring is used in the input check of parameters sent by SOAP Web Service.
For the details of method validation, refer How to define for the method for Method Validation target.
Input check details are defined in Service interface as given below.
[server projectName]-domain/src/main/java/com/example/domain/service/todo/TodoService.java
package com.example.domain.service.todo;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.validation.groups.Default;
import org.springframework.validation.annotation.Validated;
import com.example.domain.model.Todo;
@Validated // (1)
public interface TodoService {
(1) Implementation class of this interface is declared as a target for input check by applying
@Validated.
(3) Apply @Valid in the argument while carrying out input check of JavaBean.
(4) Input check can also be carried out by specifying @Validated in the group and narrowing down the
specific conditions.
Group details are explained in the JavaBean described next.
[server projectName]-model/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import java.io.Serializable;
import java.util.Date;
// (1)
public class Todo implements Serializable {
// (2)
public interface Create {
}
@Null(groups = Create.class)
@NotNull(groups = Update.class)
private String todoId;
@NotNull
@Null(groups = Create.class)
private Date createdAt;
Security measures
Authentication process
How to carry out Basic authentication in Spring Security and how to authorize in Service are introduced in the
guideline as the methods of authentication and authorization for SOAP.
WS-Security is not addressed.
For details of how to use, refer “Authentication” and “Authorization”.
A configuration example of Spring Security carrying out Basic authentication for SOAP Web Service is shown
below.
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
<sec:http pattern="/ws/**"
create-session="stateless">
<sec:csrf disabled="true" />
<sec:http-basic /> <!-- (1) -->
</sec:http>
Authorization process
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
[server projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
// omitted
// (1)
@PreAuthorize("isAuthenticated()")
public List<Todo> getTodos() {
// omitted
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
public Todo createTodo(Todo todo) {
// omitted
}
CSRF measures
SOAP Web Service should be used in the stateless communication without using a session.
Therefore, a configuration method wherein CSRF measures using the session are not employed is explained
below.
For details of CSRF, refer “CSRF Countermeasures”.
CSRF measures are enabled in the default configuration of Blank project.
Therefore, CSRF measures processing is disabled for SOAP Web Service request by adding following
configuration.
[server projectName]-web/src/main/resources/META-INF/spring/spring-security.xml
An exclusive exception class must be thrown for communicating with the client when an exception occurs in
SOAP server.
Implementation is described below.
Exception occurred at SOAP server can determine the notification message to the client by using class (SOAP-
Fault) which implements exception described henceforth.
(1) ErrorBean A class which retains code and message of occurred exception.
These exceptions are placed on [server projectName]-webservice since these are shared by SOAP server and
client.
[server projectName]-webservice/src/main/java/com/example/ws/webfault/ErrorBean.java
package com.example.ws.webfault;
[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultType.java
package com.example.ws.webfault;
[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultBean.java
package com.example.ws.webfault;
import java.util.ArrayList;
import java.util.List;
[server projectName]-webservice/src/main/java/com/example/ws/webfault/WebFaultException.java
package com.example.ws.webfault;
import java.util.List;
import javax.xml.ws.WebFault;
public WebFaultException() {
}
(2) It consists of constructor and method below, as shown in code example besides retaining the faultInfo
in the field.
This WebFaultException is inherited, and types to be communicated to the client and child class are created.
For example, child classes are created as given below.
• Authorization exception
[server projectName]-webservice/src/main/java/com/example/ws/webfault/BusinessFaultException.java
package com.example.ws.webfault;
import javax.xml.ws.WebFault;
Exception handler class is created for wrapping the run-time exceptions which occur in Service by SOAPFault.
This guideline adopts a policy wherein WebService implementation class converts and throws exceptions using
this handler.
Exception thrown from Service assumes the following. It should be added when required.
Business exception
org.terasoluna.gfw.common.exception.BusinessException
[server projectName]-web/src/main/java/com/example/ws/exception/WsExceptionHandler.java
package com.example.ws.exception;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import org.springframework.context.MessageSource;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ExceptionCodeResolver;
import org.terasoluna.gfw.common.exception.ExceptionLogger;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.exception.SystemException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.ws.webfault.WebFaultBean;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.webfault.WebFaultType;
@Component // (1)
public class WsExceptionHandler {
@Inject
MessageSource messageSource; // (2)
@Inject
ExceptionCodeResolver exceptionCodeResolver; // (3)
@Inject
ExceptionLogger exceptionLogger; // (4)
// (5)
public void translateException(Exception e) throws WebFaultException {
loggingException(e);
WebFaultBean faultInfo = null;
if (e instanceof AccessDeniedException) {
faultInfo = new WebFaultBean(WebFaultType.AccessDeniedFault);
faultInfo.addError(e.getClass().getName(), e.getMessage());
} else if (e instanceof ConstraintViolationException) {
faultInfo = new WebFaultBean(WebFaultType.ValidationFault);
this.addErrors(faultInfo, ((ConstraintViolationException) e).getConstraintViolations()
} else if (e instanceof ResourceNotFoundException) {
faultInfo = new WebFaultBean(WebFaultType.ResourceNotFoundFault);
this.addErrors(faultInfo, ((ResourceNotFoundException) e).getResultMessages());
} else if (e instanceof BusinessException) {
faultInfo = new WebFaultBean(WebFaultType.BusinessFault);
this.addErrors(faultInfo, ((BusinessException) e).getResultMessages());
} else {
// not translate.
throw new SystemException("e.ex.fw.9001", e);
}
(4) Use ExceptionLogger provided by common library and output exception information in the
exception.
For details, refer “Exception Handling”.
Exception occurred in the Service is wrapped in Web service by calling exception handler
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService {
@Inject
TodoService todoService;
@Inject
WsExceptionHandler handler; // (1)
@Override
public Todo getTodo(String todoId) throws WebFaultException /* (2) */ {
try {
return todoService.getTodo(todoId);
} catch (RuntimeException e) {
handler.translateException(e); // (3)
}
}
}
(2) Apply throws clause since the exception is thrown after wrapping in WebFaultException.
(3) In case of run-time exception, delegate the process to exception handler class.
Sending and receiving process can be carried out in SOAP by mapping Byte array while handling binary data.
However, while handling binary data of large volume, issues like memory exhaustion are likely to occur.
Accordingly, binary data can be handled as attached file in the optimised state by carrying out implementation in
compliance with MTOM(Message Transmission Optimization Mechanism).
For detailed definition, refer W3C -SOAP Message Transmission Optimization Mechanism-.
The method is described below.
[server projectName]-webservice/src/main/java/com/example/ws/todo/TodoWebService.java
package com.example.ws.todo;
import java.util.List;
import javax.activation.DataHandler;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.xml.bind.annotation.XmlMimeType;
import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;
@WebService(targetNamespace = "http://example.com/todo")
public interface TodoWebService {
// omitted
@WebMethod
void uploadFile(@XmlMimeType("application/octet-stream") /* (1) */ DataHandler dataHandler) th
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
package com.example.ws.todo;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.activation.DataHandler;
import javax.inject.Inject;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.MTOM;
import javax.xml.ws.soap.SOAPBinding;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.terasoluna.gfw.common.exception.SystemException;
import com.example.domain.model.Todo;
import com.example.domain.service.TodoService;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.exception.WsExceptionHandler;
// (1)
@MTOM
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
public class TodoWebServiceImpl extends SpringBeanAutowiringSupport implements TodoWebService {
@Inject
TodoService todoService;
// omitted
@Override
public void uploadFile(DataHandler dataHandler) throws WebFaultException {
(1) Apply @MTOM and declare the use of implementation in compliance with MTOM.
Creation of client
Project configuration
As described in “Development of Web service using JAX-WS”, model project and webservice project are assumed
to be received by SOAP server.
(2) domain project Call Web service by using WebService interface which is provided
in webservice project from Service class.
(4) model project Use class in the project for input value and return results passed to
Configure data same as SOAP SOAP server.
server.
(5) env project Define a proxy class which implements WebService interface used
while communicating with SOAP server.
Since proxy class definition is often environment dependent, it is
defined in env project.
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"><!-- (1) -->
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" /><!-- (2) -->
<!-- (3) -->
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}
</bean>
# (5)
webservice.todoWebService.wsdlDocumentResource=http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-we
(1) Define
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean.SOAP
server can be accessed through proxy class generated by this class.
(2) Define an interface that should be implemented by Web service in serviceInterface property.
(3) Details same as defined on the server side must be defined in serviceName ,portName and
namespaceUri property respectively.
Access URL configuration is not required in the client since access URL at the time of executing Web ser-
vice (end point address) is described in WSDL file. However, when a URL not described in WSDL file
is to be accessed, end point address can be overwritten by configuring endpointAddress property of
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean. It should preferably
be used while changing the environment in tests etc. Configuration example is as below.
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" />
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentReso
<property name="endpointAddress" value="${webservice.todoWebService.endpointAddress}" /><
</bean>
# (2)
webservice.todoWebService.endpointAddress=http://AAA.BBB.CCC.DDD:XXXX/[server projectName]-we
Inject Web service created above by Service and run Web service.
[client projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.soap.domain.service.todo;
import java.util.List;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import com.example.domain.model.Todo;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.todo.TodoWebService;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
TodoWebService todoWebService;
@Override
public void createTodo(Todo todo) {
// (1)
try {
todoWebService.createTodo(todo);
} catch (WebFaultException e) {
// (2)
// handle exception…
}
}
}
(2) When an exception occurs at the server side, it is wrapped in WebFaultException and sent.
Carry out process depending on the details.
For details of exception process, refer “Implementing exception handling”.
It is recommended to define proxy class in env project. This is to enable changing implementation class of Web
service by changing maven profile. When sending destination of SOAP server for testing is to be changed or when
the SOAP server is not ready, the testing can be carried out without changing another source by creating a stub
class.
When the response information is to be fetched by the client for example retry, it can be fetched by casting in
javax.xml.ws.BindingProviderclass as given below.
For details of BindingProvider, refer The Java API for XML-Based Web Services(JAX-WS) 2.2 -4.2
javax.xml.ws.BindingProvider-.
However, when Apatch CXF library is included in the dependency relation of the client, it is not possible to fetch
response information by the method given above at the time of communication error. This is because Apatch
CXF proxy is automatically used when Apatch CXF library is included in the dependency relation and Apache
CXF proxy does not retain response information in response context at the time of communication error. For error
handling of Apache CXF, refer Apache CXF Software Architecture Guide -Fault Handling-.
According to Web service and relay service which includes client of another Web service, if dependency relation
of Apache CXF library is necessarily included in the client, it must be considered as a restricted item and adequate
care must be taken.
Security measures
Authentication process
When the communication is to be established with SOAP server which uses Basic authentication while using
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean, authentication can be
done only if user name and password are added to bean definition.
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" />
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}
<!-- (1) -->
<property name="username" value="${webservice.todoWebService.username}" />
<property name="password" value="${webservice.todoWebService.password}" />
</bean>
# (2)
webservice.todoWebService.username=testuser
webservice.todoWebService.password=password
(1) Authentication information for Basic authentication can be sent by adding user name and password in
bean definition of
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean.
It is a sample wherein user name and password are transferred to the property file.
(2) Specify value of property key defined in [client projectName]-env.xml. Describe user
name and password used for authentication.
@Override
public void createTodo(Todo todo) {
try {
// (1)
todoWebService.createTodo(todo);
} catch (WebFaultException e) {
// (2)
switch (e.getFaultInfo().getType()) {
case ValidationFault:
// handle exception…
break;
case BusinessFault:
// handle exception…
break;
default:
// handle exception…
break;
}
}
Sr.No. Description
(1) Call Web service. WebFaultException must be caught since throws clause is applied.
(2) Determine exception by faultInfotype and describe respective process (sending a message to the
screen, throwing an exception etc)
Timeout configuration
Timeout that can be specified by client is broadly classified into following two types.
<bean id="todoWebService"
class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean">
<property name="serviceInterface" value="com.example.ws.todo.TodoWebService" />
<property name="serviceName" value="TodoWebService" />
<property name="portName" value="TodoWebPort" />
<property name="namespaceUri" value="http://example.com/todo" />
<property name="wsdlDocumentResource" value="${webservice.todoWebService.wsdlDocumentResource}
<!-- (1) -->
<property name="customProperties">
<map>
<!-- (2) -->
<entry key="com.sun.xml.internal.ws.connect.timeout" value-type="java.lang.Integer" va
<entry key="com.sun.xml.internal.ws.request.timeout" value-type="java.lang.Integer" va
</map>
</property>
</bean>
# (3)
webservice.request.timeout=3000
webservice.connect.timeout=3000
5.3.3 Appendix
It is recommended to add model project and webservice project to the blank project while creating SOAP server.
The method is described below.
artifactId
├-- pom.xml
├-- artifactId-domain
├-- artifactId-env
├-- artifactId-initdb
├-- artifactId-selenium
└-- artifactId-web
artifactId
├-- pom.xml
├-- artifactId-domain
├-- artifactId-env
├-- artifactId-initdb
├-- artifactId-selenium
├-- artifactId-web
├-- artifactId-model
└-- artifactId-webservice
A simple implementation of Web application like Controller etc is included in the default state of the blank
project.
SOAP web service can be used as it is, however, it is recommended to delete the same since it is not required.
For deletion target, refer “Multi-project structure of Create Web application development project ”.
artifactId-model
├-- pom.xml ... (1)
A POM (Project Object Model) file which defines model module configuration. Following are defined
in this file.
(1)
• Definition of plug-ins for dependent libraries and build
• Definition for creating jar file
<modelVersion>4.0.0</modelVersion>
<artifactId>artifactId-model</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<!-- == Begin TERASOLUNA == -->
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-common-dependencies</artifactId>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-jodatime-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-core-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- == End TERASOLUNA == -->
</dependencies>
</project>
artifactId-webservice
├-- pom.xml ... (1)
A POM (Project Object Model) file which defines webservice module configuration. Following are
defined in this file.
(1)
• Definition of plug-ins for dependent libraries and builds
• Definition for creating a jar file
<modelVersion>4.0.0</modelVersion>
<artifactId>artifactId-webservice</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>groupId</groupId>
<artifactId>artifactId</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>artifactId-model</artifactId>
</dependency>
<!-- == Begin TERASOLUNA == -->
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-common-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-jodatime-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-core-dependencies</artifactId>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-recommended-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- == End TERASOLUNA == -->
</dependencies>
</project>
[server projectName]-domain Project which stores class and configuration file related to domain layer of
SOAP server
[server projectName]-web Project which stores class and configuration file related to application layer of
SOAP server
[server projectName]-env Project which stores files dependent on the environment of SOAP server
[server projectName]-model Project which stores the class to be shared with the client and used while
executing Web service, from the classes related to domain layer of SOAP
server
[server projectName]-webservice Project which stores interface of Web service offered by SOAP server
[server projectName]-domain
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>artifactId-model</artifactId>
</dependency>
Refer “Project structure of Application Layering ” since package configuration besides these is not different from
the usual domain project.
[server projectName]-web
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>artifactId-webservice</artifactId>
</dependency>
It is not necessary to define dependency of [server projectName]-model because a transitive dependency is added
since dependency to [server projectName]-model is defined from [server projectName]-webservice.
[server projectName]-web
└ src
└ main
├ java
| └ com
| └ example
| ├ app...(1)
| └ ws...(2)
| ├ exception...(3)
| | └ WsExceptionHandler.java
| ├ abc
| | └ AbcWebServiceImpl.java
| └ def
| └ DefWebServiceImpl.java
├ resources
| ├ META-INF
| | └ spring
| | ├ applicationContext.xml...(4)
| | ├ application.properties...(5)
| | ├ spring-mvc.xml ...(6)
| | ├ spring-security.xml...(7)
| | └ [server projectName]-ws.xml...(8)
| └ i18n
| └ application-messages.properties...(9)
└ webapp
├ resources...(10)
└ WEB-INF
├ views ...(11)
└ web.xml...(12)
When only Web service is to be created in SOAP server, Spring MVC configuration file existing in the blank
project is not required, hence can be deleted.
[server projectName]-env
Since [server projectName]-env does not differ from normal env project, refer “Project structure of Application
Layering”.
[server projectName]-model
[server projectName]-model
└ src
└ main
└ java
└ com
└ example
└ domain ...(1)
└ model ...(2)
├ Xxx.java
├ Yyy.java
└ Zzz.java
(2) Package which stores the class to be used while implementing Web service in the Domain Object.
[server projectName]-webservice
[server projectName]-webservice
└ src
└ main
└ java
└ com
└ example
└ ws...(1)
├ webfault...(2)
├ abc
| └ AbcWebService.java
└ def
└ DefWebService.java
[client projectName]-domain Project which stores class and configuration file related to domain layer of
client
[client projectName]-web Project which stores class and configuration file related to application layer of
client
[client projectName]-env Project which stores files dependent on the client environment
Note: For [server projectName]-model and [server projectName]-webservice, refer ” Package configuration of
SOAP server” described earlier.
[client projectName]-domain
Following is added to pom.xml for adding dependency of [server projectName]-webservice offered from SOAP
server.
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>artifactId-webservice</artifactId>
</dependency>
Since package configuration other than this is not different from the usual domain project, refer “Project structure
of Application Layering ”.
[client projectName]-web
Since [client projectName]-web is not different from the usual web project, refer “Project structure of Application
Layering”.
[client projectName]-env
[projectName]-env
├ configs ...(1)
| └ [envName] ...(2)
| └ resources ...(3)
└ src
└ main
└ resources ...(4)
├ META-INF
| └ spring
| ├ [projectName]-env.xml ...(5)
| └ [projectName]-infra.properties ...(6)
├ dozer.properties
├ log4jdbc.properties
└ logback.xml ...(7)
(2) Directory for managing environment dependent files for each environment.
Specify a name which identifies the environment, as a directory name.
(4) Directory for managing configuration files for local development environment.
wsimport
Using wsimport
In this guideline, wsimport has been recommended to be used in the cases given in the image below..
Web service can be implemented by using wsimport while creating a client even when Domain Object or Web
service interface which are used in SOAP server cannot be used.
It is stored in the bin folder of JDK and can be used only by going through the path.
When the command is executed on the command line as given below, the source file is created in the current
directory.
# (1)
wsimport -keep -p [Package name of the source to be output] -s [Location which stores source to be
For other options, refer Java Platform, Standard Edition Tools Reference -Web Services(wsimport)-.
Note: wsimport outputs only class file as the default behaviour. No action is required only for the moving
operation, however when a debug operation is to be carried out, it is recommended to apply ‘keep’ option and
store source as well.
Although the source created is dependent on Web service to be published, Java class used in the guideline is
output.
When the class generated by wsimport is to be used in only one client project, it should be placed in the domain
project.
Although generated class belongs to infrastructure layer(Integration System Connector), it can also be included
in the normal domain project as shown in Note of Project structure.
When the generated class is to be used for multiple clients, it is preferable to create model project and webservice
project based on Project configuration is changed for SOAP server.and use by referring the same from respective
clients.
Note: Java class to be output is also output in the cases other than above. A client can be created
only by using source that has been output. However, as a policy in this guideline, since the client uses
org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean, it is recommended to
not to use another Java class.
Although JAS-VX on Java EE server is described in the guideline, JAX-WS is not implemented in case of
Tomcat.
Therefore, when SOAP server is Tomcat, Apache CXF is used as an implementation product of JAX-WS. It is
necessary to use CXFServlet by changing the configuration.
When Apache CXF is used, a couple of implementation methods of WebService class exist as given below.
In case of 1, since POJO is used as a Web service implementation class, unit testing can be easily carried out.
However, this method may not work well for AP servers other than Tomcat. Therefore, implementation by using
second method is described in the guideline instead of using first method. However, when only Tomcat is used,
using the first method is recommended due to a number of advantages.
In case of 2, implementation can be done similar to other AP servers. Operations are carried out on Java EE
server, however, this method is used when Tomcat must be mandatorily used during development.
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>3.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>3.1.4</version>
</dependency>
(2) Define mapping for the servlet that has been defined. In this case, Web service is created under
Context name/ws.
[server projectName]-web/src/main/resources/META-INF/spring/cxf-servlet.xml
</beans>
[server projectName]-web/src/main/java/com/example/ws/todo/TodoWebServiceImpl.java
package com.example.ws.todo;
import java.util.List;
import javax.inject.Inject;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.xml.ws.BindingType;
import javax.xml.ws.soap.SOAPBinding;
import org.springframework.web.context.support.SpringBeanAutowiringSupport;
import org.springframework.stereotype.Component;
import com.example.domain.model.Todo;
import com.example.domain.service.TodoService;
import com.example.ws.webfault.WebFaultException;
import com.example.ws.exception.WsExceptionHandler;
import com.example.ws.todo.TodoWebService;
// (1)
@Component
@WebService(
portName = "TodoWebPort",
serviceName = "TodoWebService",
targetNamespace = "http://example.com/todo",
endpointInterface = "com.example.ws.todo.TodoWebService")
@BindingType(SOAPBinding.SOAP12HTTP_BINDING)
// (2)
public class TodoWebServiceImpl implements TodoWebService {
// omitted
(2) Create as POJO since registration to DI container is possible by component scan. Basically, inheriting
org.springframework.web.context.support.SpringBeanAutowiringSupport
is not necessary.
Class name and address acting as SOAP end points are defined in the Bean definition file for CXFServlet.
[server projectName]-web/src/main/resources/META-INF/spring/cxf-servlet.xml
</beans>
Data Access
Todo
TBD
• About handling of unique constraint errors and pessimistic exclusion errors when using JPA
For details, refer to Overview - About exception handling.
6.1.1 Overview
This chapter explains the method of accessing the data stored in RDBMS.
JDBC datasource is implemented from Application Server, OSS library, Third-Party library, Spring Framework
etc.; hence it is necessary to select the datasource based on project requirements and deployment environment.
The typical datasources are introduced below.
When datasource is to be used in Web application, normally JDBC datasource provided by Application Server is
used.
JDBC datasource of Application Server provides functionalities required in web application such as Connection
Pooling as standard functionalities.
Apache Tomcat 7
2.
Refer to Apache Tomcat 7 User Guide (The Tomcat JDBC
Connection Pool).
Refer to Apache Tomcat 7 User Guide (JNDI Datasource
HOW-TO) (Apache Commons DBCP).
Oracle WebLogic Server 12c Refer to Oracle WebLogic Server Product Documentation.
3.
IBM WebSphere Application Server Refer to WebSphere Application Server Online informa-
4.
Version 8.5 tion center.
JBoss Enterprise Application Plat- Refer to JBoss Enterprise Application Platform Product
5.
form 6.4 Documentation.
When JDBC datasource of Application Server is not used, JDBC datasource of OSS/Third-Party library should
be used.
This guideline introduces only Apache Commons DBCP; however other libraries can also be used.
Implementation class of JDBC datasource of Spring Framework cannot be used as datasource of Web application
since it does not provide connection pooling.
In Spring Framework, implementation class and adapter class of JDBC datasource are provided; however they
are introduced as JDBC datasource classes provided by Spring Framework of Appendix, since usage is restricted.
When transactions are to be stored using Spring Framework functionality, PlatformTransactionManager needs to
be selected based on project requirements and deployment environment.
For details, refer to Settings for using transaction management of Domain Layer Implementation.
When updating data, it is necessary to execute exclusion control to ensure data consistency and integrity.
For details on exclusion control of data, refer to Exclusive Control.
The converted data access exception need not be handled in application code; however, some errors (such as
unique constraint violation, exclusion error etc.) need to be handled as per the requirements.
When handling data access exception, exception of subclass notifying error details should be caught instead of
DataAccessException.
Typical subclasses which are likely to be handled in application code are as follows:
1.
org.springframework.dao. Exception that occurs in case of unique constraint
DuplicateKeyException violation.
2.
org.springframework.dao. Exception that occurs in case of optimistic locking failure.
OptimisticLockingFailureException It occurs when same data is updated with different logic.
This exception occurs when JPA is used as O/R Mapper.
MyBatis does not have optimistic locking function; hence
this exception does not occur from O/R Mapper.
3.
org.springframework.dao. Exception that occurs in case of pessimistic locking
PessimisticLockingFailureException failure. It occurs when same data is locked with different
logic and the lock is not released even after “waiting for
unlocking” timeout period has elapsed.
Note: When optimistic locking is to be implemented using MyBatis in O/R Mapper, it should be imple-
mented as Service or Repository process.
As a method of notifying the optimistic locking failure to Controller, this guideline recommends generation
of OptimisticLockingFailureException and exception of its child class.
Todo
It has been recently found that using JPA (Hibernate) results in occurrence of unexpected errors.
See the example below for handling unique constraint violation as business exception.
try {
accountRepository.saveAndFlash(account);
} catch(DuplicateKeyException e) { // (1)
throw new BusinessException(ResultMessages.error().add("e.xx.xx.0002"), e); // (2)
}
(1) Exception (DuplicateKeyException) that occurs in case of unique constraint violation is caught.
Todo
TBD
• Conceptual diagram
• About Sequencer
Datasource settings
When using datasource defined in Application Server, it is necessary to perform settings in Bean definition file to
register the object fetched through JNDI as a bean.
Settings when PostgreSQL is used as database and Tomcat7 is used as Application Server are given below.
• xxx-env.xml
(1)
name Specify resource name. The name specified here is JNDI name.
driverClassName
Specify JDBC driver class. In the example, JDBC driver class provided by Post-
greSQL is specified.
defaultAutoCommit
Specify default value of auto commit flag. Specify ‘false’. It is forcibly set to ‘false’
when it is under Transaction Management.
- Specify JNDI name of datasource. In case of Tomcat, specify the value specified in
resource name “(1)-name” at the time of defining datasource.
(3)
When using datasource of OSS/Third-Party library or JDBC datasource of Spring Framework without using the
datasource provided by Application Server,
bean for DataSource class needs to be defined in Bean definition file.
Settings when PostgreSQL is used as database and Apache Commons DBCP is used as datasource are given
below.
• xxx-env.xml
Specify JDBC driver class. In the example, JDBC driver class provided by PostgreSQL is spec-
ified.
(2)
(3)
(4)
(5)
Specify default value of auto commit flag. Specify ‘false’. It is forcibly set to ‘false’ when it is
under Transaction Management.
(6)
(7) In BasicDataSource, configuration values common in JDBC, JDBC driver specific properties
values, connection pooling configuration values can be specified other than the values
mentioned above.
For more details about settings, refer to DBCP Configuration.
(8) In the example, values are specified directly; however, for fields where configuration values
change with the environment, actual configuration values should be specified in properties file
using Placeholder(${...}).
For Placeholder, refer to PropertyPlaceholderConfigurer of Spring Reference
Document.
For basic settings to enable transaction management, refer to Settings for using transaction management of Do-
main Layer Implementation.
For PlatformTransactionManager, the class to be used changes depending on the O/R Mapper used; hence for
detailed settings, refer to:
When more detailed information than the log output using O/R Mapper(MyBatis, Hibernate) is required, the
information output using log4jdbc(log4jdbc-remix) can be used.
For details on log4jdbc, refer to log4jdbc project page.
For details on log4jdbc-remix, refer to log4jdbc-remix project page.
• xxx-env.xml
(2)
(3)
• logback.xml
(1) Logger to output SQL execution time and SQL statement wherein the value is set in bind
variable. Since this SQL contains values for bind variables, it can be executed using DB access
tool.
(2) Logger to output SQL statement wherein the value is set in bind variable. The difference with
(1) is that SQL execution time is not output.
(3) Logger to exclude ResultSet interface, call methods of JDBC interface and to output arguments
and return values. This log is useful for analyzing the JDBC related issues; however volume of
the output log is large.
(4) Logger to output connected/disconnected events and number of connections in use. This log is
useful for analyzing connection leak, but it need not be output unless there is connection leak
issue.
(5) Logger to call methods of ResultSet interface and output arguments and return values. This log
is useful during analysis when actual result differs from expected result; however volume of the
output log is large.
(6) Logger to output the contents of ResultSet by converting them into a format so that they can be
easily verified. This log is useful during analysis when actual result differs from expected
result; however volume of the output log is large.
Warning: Large amount of log is output depending on the type of logger; hence only the required
logger should be defined or output.
In the above sample, log level for loggers which output very useful logs during development, is set to
"debug". As for other loggers, the log level needs to be set to "debug" whenever required.
When the application is to be released in performance test environment or production environ-
ment, log using log4jdbc logger should not be output at the time of normal end of process.
Typically log level should be set to "warn".
• log4jdbc.properties
# (1)
log4jdbc.dump.sql.maxlinelength=0
# (2)
(1)
(2)
Todo
TBD
• Transaction management method may change depending on the processing pattern (like Update for
multiple datasources, Update for a single datasource, Only for reference, No concurrent access etc.),
hence breakdown is planned focusing on that area.
In order to define multiple datasources and then to switch them dynamically, it is necessary to create a class that
inherits org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
and implement the conditions by which datasource is switched.
Implementation of AbstractRoutingDataSource
The datasource can be switched dynamically by using the DataSource which is created by extending
AbstractRoutingDataSourcein a same way as the normal datasource.
The example of switching the datasource based on time is given below.
package com.examples.infra.datasource;
import javax.inject.Inject;
import org.joda.time.DateTime;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
@Inject
JodaTimeDateFactory dateFactory; // (2)
@Override
protected Object determineCurrentLookupKey() { // (3)
(1)
(2)
In the method, refer to the context information (here Time) and switch the key. Here the im-
plementation should be in accordance with the business requirements. This sample is being
(4)
implemented so that the time returns different keys as “From 7:00 to 23:59” and “From 0:00 to
6:59”.
Return the key to be mapped with targetDataSources of the bean definition file described
later.
(5)
Datasource definition
Define the AbstractRoutingDataSourceextended class which was created, in bean definition file.
• xxx-env.xml
<bean id="dataSource"
class="com.examples.infra.datasource.RoutingDataSource"> <!-- (1) -->
<property name="targetDataSources"> <!-- (2) -->
<map>
<entry key="OPEN" value-ref="dataSourceOpen" />
<entry key="CLOSE" value-ref="dataSourceClose" />
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceDefault" /> <!-- (3) -->
</bean>
(1)
Define the datasource to be used. As for key, define the value that can be returned using
determineCurrentLookupKey method. In value-ref , specify the datasource to be
(2)
used for each key. Define in accordance with the number of datasources to be switched based
on Datasource settings.
This datasource is used, when key specified in determineCurrentLookupKey method
does not exist in targetDataSources. In case of implementation example, default setting
(3)
is not used; however, this time defaultTargetDataSource is being used for description
purpose.
N+1 occurs when more number of SQL statements need to be executed in accordance with the number of records
to be fetched from the database. This problem causes high load on the database and deteriorates response time.
(1) Search the records matching the search conditions from MainTable.
In the above example, col1 of MainTable fetches ’Foo’ records and the total records fetched
are 20.
(2) For each record searched in (1), related records are fetched from SubTable.
In the above example, the id column of SubTable fetches the same records as the id column of
records fetched in (1).
This SQL is executed for number of records fetched in (1).
By performing JOIN on SubTable and MainTable, records of MainTable and SubTable are fetched by executing
SQL once.
When relation of MainTable and SubTable is 1:1, check whether N+1 can be resolved using this method.
(1) When searching records matching the search conditions, the records are fetched in batch from
MainTable and SubTable, by performing JOIN on SubTable.
In the above example, col1 of MainTable collectively fetches ’Foo’ records and records of
SubTable that match the id of the records matching with search conditions.
When there are duplicate column names, it is necessary to assign alias name in order to identify
the table to which that column belongs.
If JOIN (Join Fetch) is used, all the required data can be fetched by executing SQL once.
Warning: When relation with SubTable is 1:N, the problem can be resolved using JOIN (Join Fetch);
however the following points should be noted.
• When JOIN is performed on records having 1:N relation, unnecessary data is fetched depending
on the number of records in SubTable. For details, refer to Notes during collective fetch.
• When using JPA (Hibernate), if N portions in 1:N are multiple, then it is necessary to use
java.util.Set instead of java.util.List as a collection type storage N portion.
There are cases where, it has proved better when the related records are fetched in batch for patterns with
multiple 1:N relations etc.; and then sorted by programming.
When relation with SubTable is 1:N, analyze whether the problem can be resolved using this method.
(1) Search the records matching the search conditions from MainTable.
In the above example, col1 of MainTable fetches ’Foo’ records and the total records fetched
are 20.
(2) For each record searched in (1), related records are fetched from SubTable.
Related records are not fetched one by one; but the records matching the foreign key of each
record fetched in (1), are fetched in batch.
In the above example, id column of SubTable collectively fetches same records as id column of
records fetched in (1) using IN clause.
(3) SubTable records fetched in (2) sorted and merged with records fetched in (1).
In the above example, all the required data can be fetched by executing SQL twice.
Even supposing there are 3 SubTables, SQL needs to be executed totally 4 times.
Note: This method has a special feature. It can fetch only the required data by optimizing SQL execution.
It is necessary to sort SubTable records by programming; however when there are many SubTables or when
number of N records in 1:N is more, there are cases wherein it is better to resolve the problem using this
method.
6.1.5 Appendix
When performing LIKE search, the values to be used as search conditions need to be escaped.
Following class is provided by common library as a component to perform escaping for LIKE search.
LikeConditionEscape class plays a role in absorbing the differences in wildcard characters that occur due
to difference in database and database versions.
Note: Till terasoluna-gfw-common 1.0.1.RELEASE, the characters to be escaped were 4, namely "%" , "_" ,
"%" , "_" ; however, it is changed to 2 characters namely "%" , "_" from terasoluna-gfw-common 1.0.2.RE-
LEASE in order to fix the “Bugs related to handling of wildcard characters for LIKE search ”.
In addition, a method for escaping that includes double byte characters "%" , "_" as characters to be escaped,
is also provided.
Example of escaping when default values used as characters to be escaped is given below.
"a" "a" OFF Escaping not done as the string does not contain
1.
character to be escaped.
Example of escaping when double byte characters included as characters to be escaped is given below.
For other than Sr. No. 6 and 7, refer to escaping example of default specifications.
List of escaping methods for LIKE search of QueryEscapeUtils class and LikeConditionEscape class
provided by common library is given below.
toStartingWithCondition(String)
2.
After escaping a string passed as an argument for LIKE
search, assign "%" at the end of the string after escaping.
This method is used in order to convert into a value for
Forward match search.
toEndingWithCondition(String)
3.
After escaping a string passed as an argument for LIKE
search, assign "%" at the beginning of the string after
escaping.
This method is used in order to convert into a value for
Backward match search.
toContainingCondition(String)
4.
After escaping a string passed as an argument for LIKE
search, assign "%" at the beginning and end of the string
after escaping.
This method is used in order to convert into a value for
Partial match search.
Note: Methods of No.2, 3, 4 are used when specifying the type of matching (Forward match, Backward
match and Partial match) at program side and not at SQL or JPQL side.
For example of escaping at the time of LIKE search, refer to the document for O/R Mapper to be used.
• When using MyBatis3, refer to Escape during LIKE search of Database Access (MyBatis3).
• When using JPA (Spring Data JPA), refer to Escaping at the time of LIKE searchof Database Access (JPA).
Note: API for escaping should be used as per wildcard characters supported by database to be used.
[In case of database that supports only “%” , “_” (single byte characters) as wildcard]
(1)
[In case of database that also supports “%” , “_” (double byte characters) as wildcard]
(3)
About Sequencer
The reason for creating Sequencer is that there is no mechanism to format the sequence value as string
in ID generator functionality of JPA. In actual application development, sometimes the formatted string is
also set as primary key; hence Sequencer is provided as common library.
When value set as primary key is number, it is recommended to use ID generator functionality of JPA. For
ID generator functionality of JPA, refer to How to add entities of Database Access (JPA).
The primary objective of creating Sequencer is to supplement functions which are not supported by JPA;
but it can also be used when sequence value is required in the processes not relating to JPA.
1.
Interface that defines the method to fetch subsequent sequence
org.terasoluna.gfw.common.sequencer.
value (getNext) and method to return current sequence value
Sequencer (getCurrent).
2.
Implementation class of Sequencer interface for JDBC.
org.terasoluna.gfw.common.sequencer.
This class is used to fetch sequence value by executing SQL in
JdbcSequencer
the database.
For this class, it is assumed that values are fetched from
sequence object of the database; however it is also possible to
fetch the values from other than sequence object by calling
function stored in the database.
• xxx-infra.xml
(2) Specify the datasource for executing the SQL to fetch sequence value.
• Service
// omitted
// (1)
@Inject
@Named("articleIdSequencer") // (2)
Sequencer<String> articleIdSequencer;
// omitted
@Transactional
public Article createArticle(Article inputArticle) {
return savedArticle;
}
(2) Specify bean name of the bean to be injected in value attribute of @javax.inject.Named
annotation.
In the above example, bean name ("articleIdSequencer") defined in
xxx-infra.xml is specified.
(3) Call Sequencer#getNext() method and fetch the subsequent sequence value.
In the above example, fetched sequence value is used as Entity ID.
When fetching current sequence value, call Sequencer#getCurrent() method.
Tip: When Sequencer for which bean is defined is 1, @Named annotation can be omitted. When
specifying multiple sequencers, bean name needs to be specified using @Named annotation.
Classes of Spring Framework which play a role in converting an exception to data access exception, are as follows:
Spring Framework provides implementation of JDBC datasource. However since they are very simple classes,
they are rarely used in production environment.
These classes are mainly used during Unit Testing.
Spring Framework provides adapter classes with extended JDBC datasource operations.
Specific adapter classes are introduced below.
6.2.1 Overview
This guideline presumes the use of Mapper interface of MyBatis3 as a Repository interface. Refer to “Implemen-
tation of Repository” for Repository interface.
The architecture to access database by using MyBatis3 and MyBatis-Spring is explained in the Overview.
For more information, refer “How to use”.
About MyBatis3
MyBatis3 is a type of O/R mapper which is developed for mapping SQL to objects. and not for mapping the
records stored in database to objects.
Thus, it is an effective O/R mapper to access denormalized databases or to obtain full control of SQL execution
in an application without entrusting the SQL statement execution to the O/R mapper.
In this guideline, CRUD operation of Entity is performed by using Mapper interface added from MyBatis3. Refer
to “Mapper interface mechanism” for details of Mapper interface.
This guideline does not cover explanation of all the functionalities of MyBatis3, Hence, it is recommended to refer
“MyBatis 3 REFERENCE DOCUMENTATION ” .
The explanation about main components of MyBatis3 (configuration file) is given below.
In MyBatis3, SQL execution and O/R mapping is implemented by integrating the following components with
each other based on the definition of configuration file.
Flow by which main components of MyBatis3 access the database, is explained below.
Process for accessing database can be broadly divided into 2 types.
• Processes that are performed at the start of the application. Processes (1) to (3) mentioned below correspond
to this type.
• Processes that are performed for each request from the client. Processes (4) to (10) mentioned below
Processes that are performed at the start of the application are executed with following flow.
Refer to “Component structure of MyBatis-Spring” for the flow when integrating with Spring.
Processes that are performed for each request from the client are executed with the following flow.
Refer to “Component structure of MyBatis-Spring” for the flow when integrating with Spring.
SqlSession fetches the SQL to be executed from mapping file and executes SQL.
10.
Commit and rollback for the transaction are performed by calling SqlSession API from application
code. However, it is not described in the above flow.
When integrated with Spring, Spring transaction control functionality performs commit and rollback. As a
result, the API controlling SqlSession is not called directly from application class.
• MyBatis3 SQL can be executed in the transactions managed by Spring. Hence, it is not necessary to control
the transactions that are dependent on MyBatis3 API.
• The overall initialization process for using MyBatis3 is performed by MyBatis-Spring API. Hence, My-
Batis3 API need not be used directly.
• Mapper object can be injected in Singleton Service class to generate a thread safe Mapper object.
This guideline does not cover explanation of all the functionalities of MyBatis-Spring, Hence, it is recommended
to refer “Mybatis-Spring REFERENCE DOCUMENTATION ” .
The flow by which the main components of MyBatis-Spring access the database is explained below. Processes to
access database can be broadly divided into two types.
• Processes that are performed at the start of the application. Processes (1) to (4) mentioned below correspond
to this type.
• Processes that are performed for each request from the client. Processes (5) to (11) mentioned below
correspond to this type.
Processes that are performed at the start of the application are executed by the following flow.
Processes that are performed for each request from client are executed by the following flow.
Application (Service) calls the method of Mapper object (Proxy object that imple-
6.
ments Mapper interface) injected by DI container.
Refer to “Mapper interface mechanism” for Mapper interface mechanism.
Mapper object calls SqlSession (SqlSessionTemplate ) method correspond-
7.
ing to the called method.
Proxy enabled and thread safe SqlSession uses MyBatis3 standard SqlSession
9.
assigned to the transaction.
When SqlSession assigned to the transaction does not exist,
SqlSessionFactory method is called to fetch SqlSession of standard
MyBatis3.
SqlSessionFactory returns MyBatis3 standard SqlSession .
10.
Since the returned MyBatis3 standard SqlSession is assigned to the transaction, if
it is within the same transaction, same SqlSession is used without creating a new
one.
MyBatis3 standard SqlSession fetches SQL to be executed from mapping file and
11.
executes the SQL.
Although it is not explained in the flow, the commit and rollback of transaction is performed by Spring
transaction control function.
Refer to “Regarding transaction management” for how to control transaction using Spring transaction
control function.
Actual configuration and implementation methods for accessing database using MyBatis3 are explained below.
pom.xml settings
<modelVersion>4.0.0</modelVersion>
<artifactId>projectName-domain</artifactId>
<packaging>jar</packaging>
<parent>
<groupId>com.example</groupId>
<artifactId>mybatis3-example-app</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<dependencies>
</dependencies>
</project>
When terasoluna-gfw-parent project is not specified as a parent project, it becomes necessary to specify
individual version as well.
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-mybatis3-dependencies</artifactId>
<version>5.2.0.RELEASE</version>
<type>pom</type>
</dependency>
In the above example, 5.2.0.RELEASE is specified. However, version used in the project should be speci-
fied.
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-mybatis3-dependencies</artifactId>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
</exclusion>
</exclusions>
</dependency>
Datasource settings
When MyBatis3 and Spring are integrated, the datasource managed by Spring DI container should be used.
• projectName-env/src/main/resources/META-INF/spring/projectName-env.xml
</beans>
If a value other than transactionManager is specified, the same value must be specified in transaction-
manager attribute of <tx:annotation-driven> tag.
• projectName-env/src/main/resources/META-INF/spring/projectName-env.xml
<tx:jta-transaction-manager />
</beans>
MyBatis-Spring settings
When MyBatis3 and Spring are integrated, it is necessary to carry out following
• Generation of SqlSessionFactory that customizes the processes necessary for integrating MyBatis3
and Spring
• projectName-domain/src/main/resources/META-INF/spring/projectName-infra.xml
</beans>
Define <mybatis:scan> for scanning Mapper interface and specify the base pack-
4.
age that stores the Mapper interface in base-package attribute.
The Mapper interface stored under specified package is scanned and a thread safe
Mapper object (Proxy object of Mapper interface) is automatically generated.
[Package decided for each project should be the specified package]
When SqlSessionFactoryBean is used, MyBatis3 configuration can be specified directly in the bean
property rather than MyBatis configuration file. However, in this guideline, it is recommended to specify
MyBatis3 settings in the MyBatis standard configuration file.
MyBatis3 settings
Only those configuration fields that are not dependent on application characteristics are explained here.
fetchSize settings
When a query that returns a large amount of data is to be described, an appropriate fetchSize must be
specified for JDBC driver.
fetchSize is a parameter that specifies data record count that can be fetched in a single communication
between JDBC driver and database.
Since default value of JDBC driver is used if fetchSize is not specified, following issues are likely to appear
due to JDBC driver that is being used.
• “Out of memory” when default value of JDBC driver is large or has no restriction
MyBatis3 can specify fetchSize by using 2 methods shown below to control the occurrences of these issues.
mybatis3 5.2.0.RELEASE.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<settings>
<!-- (1) -->
<setting name="defaultFetchSize" value="100" />
</settings>
</configuration>
When fetchSize is to be specified in query unit, a value must be specified in fetchSize attribute of
XML element (<select> element) to describe SQL for search.
Note that, when a query for returning large volume of data is to be described, usage of “Implementation of Re-
sultHandler” must also be explored.
The mode to be used should be determined based on characteristics and constraints of each mode, and
performance requirements.
Refer to “Using SQL execution mode” for how to configure an execution mode.
TypeAlias settings
When TypeAlias is used, an alias (short name) can be assigned for the Java class specified in mapping file.
When TypeAlias is not used, it is necessary to specify the fully qualified class name (FQCN) of Java class in
type attribute, parameterType attribute and resultType attribute specified in a mapping file As a result,
decrease in description efficiency and increase in typographical errors of mapping file are the areas of concern.
In this guideline, it is recommended to use TypeAlias to improve description efficiency, reduce typographical
errors and improve readability.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
A method to configure TypeAlias in class unit and a method to clearly specify an alias are provided in Type
Alias settings. Refer to “TypeAlias settings” of Appendix for details.
<mapper namespace="com.example.domain.repository.account.AccountRepository">
<resultMap id="accountResultMap"
type="Account">
<!-- omitted -->
</resultMap>
<select id="findOne"
parameterType="string"
resultMap="accountResultMap">
<!-- omitted -->
</select>
<select id="findByCriteria"
parameterType="AccountSearchCriteria"
resultMap="accountResultMap">
<!-- omitted -->
</select>
</mapper>
An alias name is already configured for general Java classes like primitive type and primitive wrapper type.
An error may occur when setting the column value to null for a database that is being used (JDBC driver).
This issue can be resolved by the JDBC driver by configuring null value and specifying a recognizable JDBC
type.
When setting null value, if an error accompanied by following stack traces occurs, mapping of null value and
JDBC type becomes necessary.
By default, a generic JDBC type called OTHER is specified in MyBatis3. However, an error may occur in JDBC
driver due to OTHER.
When Oracle is used as a database and if default settings is used as is, it has been confirmed that errors
occur. Although behavior may change depending on the version, it should be described as ‘change in the
settings may be required when Oracle is used’.
The version wherein error is confirmed to occur in Oracle 11g R1. The error can be resolved by changing
the settings wherein NULL type of JDBC type is mapped.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<settings>
<!-- (1) -->
<setting name="jdbcTypeForNull" value="NULL" />
</settings>
</configuration>
As an alternate method of resolving the error, an appropriate JDBC type supporting the Java type is set in
the inline parameters of property wherein null value may be set.
However, when JDBC type is individually set in inline parameter, description content of mapping file and
occurrence of specified mistakes may increase. Hence, in this guideline, it is recommended to resolve the
errors in the overall configuration. If errors are not resolved even after changing the overall configuration,
individual setting can be applied only for the property wherein an error has occurred.
TypeHandler settings
A TypeHandler is provided by MyBatis3 for general Java classes like primitive type and primitive wrapper
type class. Specific settings are not required.
When a class which represents date and time offered by JSR-310 Date and Time API in MyBatis3 is used,
TypeHandler offered by a library different from that of MyBatis (mybatis-typehandlers-jsr310 )
is used. While using, configuration to recognise TypeHandler is added to mybatis-config.xml , in My-
Batis.
<typeHandlers>
<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" /> <!-- (1) -->
<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" /> <!-- (2) -->
<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" /> <!-- (3) -->
<typeHandler handler="org.apache.ibatis.type.LocalTimeTypeHandler" /> <!-- (4) -->
<typeHandler handler="org.apache.ibatis.type.OffsetDateTimeTypeHandler" /> <!-- (5) -->
<typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler" /> <!-- (6) -->
<typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler" /> <!-- (7) -->
<typeHandler handler="org.apache.ibatis.type.YearTypeHandler" /> <!-- (8) -->
<typeHandler handler="org.apache.ibatis.type.MonthTypeHandler" /> <!-- (9) -->
</typeHandlers>
Tip: Since TypeHandler is auto-detected in MyBatis 3.4, above configuration is not required.
Enum type is mapped with a constant identifier (string) of Enum in the default behavior of MyBatis3.
In case of Enum type shown below, it is mapped with strings like "WAITING_FOR_ACTIVE" , "ACTIVE" ,
"EXPIRED" , "LOCKED" and stored in the table.
package com.example.domain.model;
In MyBatis, Enum type can be mapped with the numeric value (order in which the constants are defined). For how
to map Enum type with a numeric value, refer to “MyBatis 3 REFERENCE DOCUMENTATION(Configuration
XML-Handling Enums-) ”.
Creating a TypeHandler is required while mapping a Java class and JDBC type not supported by MyBatis3.
• A file data with large capacity (binary data) is retained in java.io.InputStream type and mapped in
BLOB type of JDBC type.
• A large capacity text data is retained as java.io.Reader type and mapped in CLOB type of JDBC type.
• etc ...
Refer to “Implementation of TypeHandler” for creating the three types of TypeHandler described above.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<typeHandlers>
<!-- (1) -->
<package name="com.example.infra.mybatis.typehandler" />
</typeHandlers>
</configuration>
Tip: In the above example, although TypeHandler stored under specified package is automatically
detected by MyBatis, it can also be configured in class unit.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<typeHandlers>
<typeHandler handler="xxx.yyy.zzz.CustomTypeHandler" />
<package name="com.example.infra.mybatis.typehandler" />
</typeHandlers>
• projectName-domain/src/main/resources/META-INF/spring/projectName-infra.xml
</beans>
The mapping of Java class wherein TypeHandler is applied and JDBC type is specified as below.
Tip: Although each of the above example is a configuration method to be applied to overall applica-
tion, an individual TypeHandler can also be specified for each field. It is used while overwriting a
TypeHandler that is applicable for overall application.
id
,image_data
,created_at
)
VALUES
(
#{id}
/* (3) */
,#{imageData,typeHandler=XxxBlobInputStreamTypeHandler}
,#{createdAt}
)
</insert>
</mapper>
A basic implementation method for accessing a database by using MyBatis3 function is explained below.
package com.example.domain.repository.todo;
// (1)
public interface TodoRepository {
}
The mapping file can be stored at either of the locations given below.
• A directory that conforms to the determined rules to enable MyBatis3 to automatically read the map-
ping file
• An arbitrary directory
In this guideline, it is recommended to use a mechanism wherein mapping file is stored in the direc-
tory conforming to the rules determined by MyBatis3 thus enabling automatic reading of file.
It is necessary to store the mapping file on the class path at a level same as the package hierarchy of
Repository interface to enable automatic reading of mapping file.
How to implement a CRUD process and considerations when implementing SQL are explained here.
How to implement following processes for the basic CRUD operation is explained.
Note: It is important to note that the searched Entity is cached in the area called as local cache while
implementing CRUD process by using MyBatis3.
• Entity is cached for each “statement ID + pattern of built SQL + parameter value bound to the built
SQL + page position (fetch range)”.
In other words, when all the search APIs provided by MyBatis3 are called by the same parameter in a
process within the same transaction, the instance of cached Entity is returned without executing the SQL
from 2nd time onwards.
Here, it should be noted that Entity returned by MyBatis API and Entity managed by local cache
consist of the same instance.
Tip: Local cache can also be changed so as to be managed in statement unit. When the local cache is to
be managed in statement unit, MyBatis executes SQL each time and fetches the latest Entity.
Before explaining the basic implementation, the components to be registered are explained below.
Note: In this guideline, Mapper interface of MyBatis3 is called as Repository interface in order to stan-
dardize the architecture terminology
The explanation hereafter is given presuming that the user has read “Implementation of Entity” “Implementation
of Repository” and “Implementation of Service”.
How to map a JavaBean in the search results is explained before explaining the search process of Entity.
Two methods of automatic and manual, are provided in MyBatis3 to map JavaBean (Entity) in the search re-
sults (ResultSet). Since both the methods have distinct features, a mapping method to be used should be
determined by considering the project features and features of SQL to be executed by the application.
• Automatic mapping is used for simple mapping (mapping to a single object) whereas manual mapping
is used in case of advanced mapping (mapping to related objects) is necessary.
It is not mandatory to use any one of the two methods proposed above and they can be considered as one
of the alternatives.
Architect should clearly identify the criteria for selecting manual mapping and automatic mapping
for the programmers and look for a uniform mapping method for the entire application.
The respective features and examples for automatic mapping and manual mapping are explained below.
In MyBatis3, a mechanism which automatically performs the mapping by matching column name and property
name is provided as a method to map search result (ResultSet) column and JavaBean property.
When automatic mapping is used, only SQL to be executed is described in the mapping file thus reducing
the description content of mapping file.
By reducing the description, simple mistakes and modification locations while changing a column name or
a property name can be reduced as well.
However, automatic mapping can only be used for single object. Manual mapping is required when map-
ping for the nested related objects.
Column name mentioned here does not signify the physical column name of a table but refers to the column
which contains the search result (ResultSet) fetched by executing the SQL. Therefore, by using AS
clause, matching a physical column name and a JavaBean property name is comparatively easier.
How to map search results in JavaBean using automatic mapping is shown below.
• projectName-domain/src/main/resources/com/example/domain/repository/todo/TodoReposi
</mapper>
When physical column name of table and JavaBean property name match, there is no
2.
need to specify the AS clause.
• JavaBean
package com.example.domain.model;
import java.io.Serializable;
import java.util.Date;
Tip: How to map a column name separated by an underscore and a property name in camel case
format
In the above example, the difference between a column name separated by an underscore and a property
name in camel case format is resolved by using AS clause. However, it can be implemented by changing
MyBatis3 configuration if only the difference between a column name separated by an underscore and a
property name in camel case format is to be resolved.
When the physical column name of a table is separated by an underscore, automatic mapping can be per-
formed in JavaBean property in the camel case format by adding following settings to MyBatis configuration
file (mybatis-config.xml).
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<settings>
<!-- (3) -->
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
• projectName-domain/src/main/resources/com/example/domain/repository/todo/TodoReposi
</mapper>
MyBatis3 provides a mechanism to manually map search result (ResultSet) column and JavaBean property by
defining their association in the mapping file.
When manual mapping is used, the association between search result (ResultSet) column and JavaBean
property is defined for each item one by one in the mapping file. Therefore, mapping with extremely high
flexibility and complexity can be achieved.
Manual mapping is a method to effectively map the search results (ResultSet) column and JavaBean
property for the cases given below.
• When data model (JavaBean) that handles the application and physical table layout do not match
Also, manual mapping can be mapped efficiently compared with automatic mapping. If prevail efficiency
of processing, it is desirable to use manual mapping instead of automatic mapping.
How to map search results in JavaBean using manual mapping is given below.
Since the idea is to explain how to use manual mapping, a simple example wherein automatic mapping can also
be performed, is used for the explanation.
• projectName-domain/src/main/resources/com/example/domain/repository/todo/TodoReposi
</mapper>
<id> element and <result> element can both be used for mapping search results (ResultSet) column
and JavaBean property. However, it is recommended to use <id> element for mapping of ID (PK) column.
This is because, when <id> element is used for the mapping of ID (PK) column, the performance of
mapping process for related objects and the cache control process of objects provided by MyBatis3 can
show overall improvement.
How to implement a search process of Entity for different purposes, is explained below.
Read “How to map a JavaBean in Search results” before reading how to implement the search process for Entity.
The explanation below is the example wherein a setting is enabled to automatically map column name separated
by an underscore in property name with camel case.
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
</configuration>
Implementation example wherein, a single Entity is fetched by specifying PK rather than configuring PK in a
single column, is given below.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
// (1)
Todo findOne(String todoId);
</mapper>
In case of a simple type like String, there is no restriction for the bind variable name, however, it is
recommended to use the value same as the argument name of the method.
• Apply DI to Repository in Service class and call the interface method of Repository.
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
import com.example.domain.repository.todo.TodoRepository;
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
// (5)
@Inject
TodoRepository todoRepository;
@Transactional(readOnly = true)
@Override
public Todo getTodo(String todoId) {
// (6)
Todo todo = todoRepository.findOne(todoId);
if (todo == null) { // (7)
throw new ResourceNotFoundException(ResultMessages.error().add(
"e.ex.td.5001", todoId));
}
return todo;
}
Since null is returned when the search result shows 0 records, if required, implement
7.
the process when Entity cannot be fetched.
In the above example, when Entity cannot be fetched, “resource not detected” error is
generated.
The implementation example of fetching a single Entity by specifying PK rather than configuring PK in multiple
columns, is given below.
Basic settings are same as while configuring PK in a single column, however the way to specify a method
argument for Repository interface is different.
package com.example.domain.repository.order;
import org.apache.ibatis.annotations.Param;
import com.example.domain.model.OrderHistory;
// (1)
OrderHistory findOne(@Param("orderId") String orderId,
@Param("historyId") int historyId);
When multiple method arguments of Repository interface are specified, it is recommended to specify
@org.apache.ibatis.annotations.Param annotation in the argument. “Bind variable name”
specified while selecting the value from mapping file is specified in the value attribute of @Param anno-
tation.
As shown in the above example, the value specified in the argument can be bound in SQL by specifying
#{orderId} and #{historyId} from mapping file.
</mapper>
Although it is not mandatory to specify @Param annotation, if it is not specified, a mechanical bind variable
name needs to be specified as given below. The bind variable name when @Param annotation is not
specified is formed as, ” “param” + declared position of the argument(start from 1)”, and thus can hamper
maintainability and readability of the source code.
WHERE
order_id = #{param1}
AND
history_id = #{param2}
Entity search
Implementation example is given below wherein a SQL with search results 0 to N records is executed and multiple
records of Entity are fetched.
Warning: If the search results data is in a large quantity, using “Implementation of ResultHandler”
should be considered.
package com.example.domain.repository.todo;
import java.util.List;
import com.example.domain.model.Todo;
// (1)
List<Todo> findAllByCriteria(TodoCriteria criteria);
Tip: In the above example, the return value of method is specified as java.util.List however, search
results can also be received as java.util.Map.
When search results are received by Map, java.util.HashMap instance is returned. Hence, it should
be noted that the alignment sequence of Mapis not guaranteed.
package com.example.domain.repository.todo;
import java.util.Map;
import com.example.domain.model.Todo;
import org.apache.ibatis.annotations.MapKey;
@MapKey("todoId")
Map<String, Todo> findAllByCriteria(TodoCriteria criteria);
package com.example.domain.repository.todo;
import java.io.Serializable;
import java.util.Date;
Although it is not mandatory to create a JavaBean for retaining search conditions, it is recommended to
create one, to clearly identify the role of the stored value. However, implementation can also be performed
without creating a JavaBean.
The decision standards of the cases for which JavaBean is created and those for which it is not created
should be clearly stated to the programmers by the Architect, so that an overall uniform application
can be created.
package com.example.domain.repository.todo;
import java.util.List;
import com.example.domain.model.Todo;
When JavaBean is not created, the search conditions are declared one by one in an argument and “bind
variable name” is specified in value attribute of @Param annotation. Multiple search conditions can be
passed to SQL by defining the method described above.
</mapper>
When a XML character ("<" or ">" etc.) that needs to be escaped in SQL is specified, the readability
of SQL can be maintained by using CDATA section. When CDATA section is not used, entity reference
characters such as "<", ">" need to be specified, and may lead to reduced SQL readability.
In the above example, CDATA section is specified since "<" is used as the condition for created_at.
The implementation example of fetching Entity records matching with search conditions is given below.
• Defining the method for fetching Entity records matching with search conditions.
package com.example.domain.repository.todo;
// (1)
long countByFinished(boolean finished);
FROM
t_todo
WHERE
finished = #{finished}
</select>
</mapper>
"_" (underscore) should be specified at the beginning of the primitive type alias name. When
"_"(underscore) is not specified, it is handled as primitive wrapper type (java.lang.Long etc.) alias.
Implementation example to search an Entity by using MyBatis3 functionality for specifying the fetching scope, is
given below.
Warning: Precautions when large number of data records match the search conditions
Standard MyBatis method is to move the cursor and skip the data which is outside the fetch range of
search results (ResultSet). Hence, in proportion to the data records that match with search condi-
tions, issues like memory exhaustion or performance degradation of cursor movement are more likely
to occur.
According to JDBC result set type, the cursor movement processing supports following 2 types. Default
behavior is dependent on the default result set type of JDBC driver.
• When result set type is FORWARD_ONLY, ResultSet#next() is repeatedly called and data
outside the fetching range is skipped.
• When result set type is SCROLL_SENSITIVE or SCROLL_INSENSITIVE,
ResultSet#absolute(int) is called and data outside the scope of fetching range
is skipped.
Performance degradation can be restricted to a minimum by using ResultSet#absolute(int)
however, it is dependent on the implementation of JDBC driver. If process same as
ResultSet#next() is performed internally, it is not possible to prevent memory exhaustion or
performance deterioration.
When there is a possibility of large number of data records matching the search conditions, SQL
refine method should be adopted instead of pagination search which is a MyBatis3 standard
method.
ackage com.example.domain.repository.todo;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
import com.example.domain.model.Todo;
// (1)
long countByCriteria(TodoCriteria criteria);
// (2)
List<Todo> findPageByCriteria(TodoCriteria criteria,
RowBounds rowBounds);
Define the method that extracts those Entities that fall in the fetching range from the
2.
Entities matching with search conditions.
RowBounds that retains the information of fetch range (offset and limit) is specified
as the argument of defined method.
Since MyBatis3 performs the process to extract records of corresponding range from the search results, it
is not necessary to filter the records within the fetch range using SQL.
ORDER BY
todo_id
]]>
</select>
</mapper>
When pagination search is performed, it is recommended to standardize the WHERE clause specified in
“SQL that fetches total number of records for Entities matching with search condition” and “SQL that
fetches the list of the Entities matching with search conditions”, using include function of MyBatis3.
A standardized WHERE clause of above SQL is defined as below. Refer to “Sharing SQL statement” for
details.
<sql id="findPageByCriteriaWherePhrase">
<![CDATA[
WHERE
todo_title LIKE #{title} || '%' ESCAPE '~'
AND
created_at < #{createdAt}
]]>
</sql>
Result set type is specified in resultType attribute when it is to be specified explicitly. When
the default result set type of JDBC driver is FORWARD_ONLY, it is recommended to specify
SCROLL_INSENSITIVE.
// omitted
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
TodoRepository todoRepository;
// omitted
@Transactional(readOnly = true)
@Override
public Page<Todo> searchTodos(TodoCriteria criteria, Pageable pageable) {
// (3)
long total = todoRepository.countByCriteria(criteria);
List<Todo> todos;
if (0 < total) {
// (4)
RowBounds rowBounds = new RowBounds(pageable.getOffset(),
pageable.getPageSize());
// (5)
todos = todoRepository.findPageByCriteria(criteria, rowBounds);
} else {
// (6)
todos = Collections.emptyList();
}
// (7)
return new PageImpl<>(todos, pageable, total);
}
// omitted
Generate RowBounds object that specifies fetch range of pagination search when
4.
Entities matching with search conditions exist.
Specify “skip record” in the first argument (offset) and “maximum fetch records”
in the second argument (limit) of RowBounds. For the values to be specified
as argument, it is advisable to specify the values fetched by calling getOffset
method and getPageSize method of Pageable object provided by Spring Data
Commons.
Basically, the fetch range is
• Records 1st to 20th when 0 is specified in offset and 20 is specified in limit
• Records 21st to 40th when 20is specified in offset and 20 is specified in limit
Call Repository method and fetch Entities in the fetch range that match with search
5.
conditions.
When the Entities that match with search conditions do not exist, set empty list in the
6.
search results.
Implementation example to search an Entity by using range search mechanism provided by database, is given
below.
Since SQL refinement method uses range search mechanism provided by database, Entity of fetch range can be
fetched efficiently as compared to standard method of MyBatis3.
Note: It is recommended to adopt the SQL refining method when a large volume of data matching
with search condition exists.
package com.example.domain.repository.todo;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import org.springframework.data.domain.Pageable;
import com.example.domain.model.Todo;
// (1)
long countByCriteria(
@Param("criteria") TodoCriteria criteria);
// (2)
List<Todo> findPageByCriteria(
@Param("criteria") TodoCriteria criteria,
@Param("pageable") Pageable pageable);
}
Define a method to extract entities that can be fetched from the Entities matching with
2.
search conditions.
org.springframework.data.domain.Pageable that retains the informa-
tion within the fetch range (offset and limit) is specified as an argument for defined
method.
Note: Reason why the argument specifies @Param annotation for a single method
In the above example, the argument specifies @Param annotation for a single method
(countByCriteria). This is to standardize WHERE clause and the SQL executed when
findPageByCriteria method is called.
By specifying bind variable name in the argument using @Paramannotation, nested structure of bind vari-
able name specified in SQL is combined.
<sql id="findPageByCriteriaWherePhrase">
<![CDATA[
/* (3) */
WHERE
todo_title LIKE #{criteria.title} || '%' ESCAPE '~'
AND
created_at < #{criteria.createdAt}
]]>
</sql>
</mapper>
// omitted
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
TodoRepository todoRepository;
// omitted
@Transactional(readOnly = true)
@Override
public Page<Todo> searchTodos(TodoCriteria criteria,
Pageable pageable) {
long total = todoRepository.countByCriteria(criteria);
List<Todo> todos;
if (0 < total) {
// (5)
todos = todoRepository.findPageByCriteria(criteria,
pageable);
} else {
todos = Collections.emptyList();
}
return new PageImpl<>(todos, pageable, total);
}
// omitted
how to register an Entity for different purposes is explained with implementation example.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
// (1)
void create(Todo todo);
Return value for the method that registers Entity can be void.
However, when SQL that inserts selected results is executed, boolean or numeric value type (int or
long) should be set as the return value based on application requirements.
• When boolean is specified as return value, false is returned when 0 records are registered and
true is returned when 1 or more records are registered.
• When numeric value type is specified as return value, number of registered records is returned.
</mapper>
package com.example.domain.service.todo;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
import com.example.domain.model.Todo;
import com.example.domain.repository.todo.TodoRepository;
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
// (4)
@Inject
TodoRepository todoRepository;
@Inject
JodaTimeDateFactory dateFactory;
@Override
public Todo create(Todo todo) {
// (5)
todo.setTodoId(UUID.randomUUID().toString());
todo.setCreatedAt(dateFactory.newDate());
todo.setFinished(false);
todo.setVersion(1);
// (6)
todoRepository.create(todo);
// (7)
return todo;
}
Set the value for Entity object passed in the argument based on the application re-
5.
quirements.
In the above example,
• “UUID” as an ID
• “System date and time” as registration date and time”
• “false : Incomplete” in the completion flag
• “1” in the version
are set.
Call Repository interface method and register the single Entity record.
6.
Generating key
An implementation example is given in “Registering a single Entity record” for generating key (ID) in Service
class.
When a database function (function or ID column etc.) is used for generating key, it is recommended to
use the mechanism of MyBatis3 key generation functionality.
• A method wherein the result obtained by calling the function etc. provided by database, is handled as the
key
• A method wherein the result obtained by calling ID column provided by database (IDENTITY type,
AUTO_INCREMENT type etc.) + Statement#getGeneratedKeys() added by JDBC3.0 is han-
dled as the key.
Method wherein result obtained by calling the function etc. provided by database is handled as a key, is explained
first. In the example given below, H2 Database is used as the database.
</mapper>
Next, the method wherein result obtained by calling ID column provided by database +
Statement#getGeneratedKeys() added by JDBC3.0ratedKeys() is handled as a key, is explained.
In the example given below, H2 Database is used as the database.
created_at,
)
VALUES
(
#{level},
#{message},
#{createdAt},
)
</insert>
</mapper>
• Execute INSERT statement that registers multiple records at the same time.
Refer to “Using batch mode” for details on how to use the JDBC batch update functionality.
How to execute the INSERT statement that registers multiple records at the same time, is explained below. In the
example below, H2 Database is used as the database.
package com.example.domain.repository.todo;
import java.util.List;
import com.example.domain.model.Todo;
// (1)
void createAll(List<Todo> todos);
</mapper>
- Repeats the process for the list of Todo objects passed as argument, by
3.
using foreach element.
For details of foreach details, refer to “MyBatis3 REFERENCE DOC-
UMENTATION (Dynamic SQL-foreach-)”.
collection Specifies the collection for processing.
In the example given above, the process is repeated for the list of Reposi-
tory method arguments. When @Param is not specified in the argument of
Repository method, "list" is specified. When @Param is specified, the
value specified in value attribute of @Param is specified.
item Specifies the local variable name that retains one element from the list.
JavaBean property can be accessed from the SQL in foreach element, in
#{Local variable name.Property name} format.
separator Specifies the string to separate elements in the list.
In the above example, by specifying ",", the VALUE clause for each ele-
ment is separated with ",".
Note: ** Precautions when using SQL that registers multiple records at the same time**
When SQL that registers multiple records concurrently is executed, “Generating key” described earlier
cannot be used.
INSERT INTO
t_todo
(
todo_id,
todo_title,
finished,
created_at,
version
)
VALUES
(
'99243507-1b02-45b6-bfb6-d9b89f044e2d',
'todo title 1',
false,
'09/17/2014 23:59:59.999',
1
)
,
(
'66b096f1-791f-412f-9a0a-ee4a3a9186c2',
'todo title 2',
0,
'09/17/2014 23:59:59.999',
1
)
Tip: The support status and syntax for the SQL that performs batch registration differ depending on
database and version. The links for reference pages of major databases are given below.
• Oracle 12c
• DB2 10.5
• PostgreSQL 9.4
• MySQL 5.7
Entity update method for different purposes is explained with implementation example.
Note: Hereafter, an implementation example is explained wherein optimistic locking is performed by us-
ing version column. However, process related to optimistic locking need not be performed when optimistic
locking is not required.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
// (1)
boolean update(Todo todo);
The return value of the method that updates single Entity can be boolean.
However, when multiple records are obtained as update result and it is necessary to handle it as data
mismatch error, numeric value type (int or long) needs to be specified as return value and it needs to be
checked that a single update record exists. When main key is used as the update condition, return value can
be set as boolean since multiple records are not obtained as update result.
• When boolean is specified as return value, false is returned when update records are 0 and trueis
returned when update records are 1 or more.
• When numeric value is specified as return value, number of update records is returned.
finished = #{finished},
version = version + 1
WHERE
todo_id = #{todoId}
AND
version = #{version}
</update>
</mapper>
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
import com.example.domain.repository.todo.TodoRepository;
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
// (3)
@Inject
TodoRepository todoRepository;
@Override
public Todo update(Todo todo) {
// (4)
Todo currentTodo = todoRepository.findOne(todo.getTodoId());
// (5)
currentTodo.setTodoTitle(todo.getTodoTitle());
currentTodo.setFinished(todo.isFinished());
// (6)
boolean updated = todoRepository.update(currentTodo);
// (7)
if (!updated) {
throw new ObjectOptimisticLockingFailureException(Todo.class,
currentTodo.getTodoId());
}
currentTodo.setVersion(todo.getVersion() + 1);
return currentTodo;
}
currentTodo.setVersion(todo.getVersion() + 1);
is obtained.
It is a process to combine the version updated in database and the version that stores an Entity.
If database status and Entity status are not matched when referring a version in the call source (Controller
or JSP etc.) process, data mismatch may occur and application is not executed as anticipated.
Refer to “Using batch mode” for the details on how to use the JDBC batch update functionality.
How to execute UPDATE statement that concurrently updates multiple records is explained here.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
import org.apache.ibatis.annotations.Param;
import java.util.List;
// (1)
int updateFinishedByTodIds(@Param("finished") boolean finished,
@Param("todoIds") List<String> todoIds);
Note: Return value for the method that updates Entity in batch
Return value of the method that updates Entity in batch should preferably be of numeric type (int or
long). When it is set as numeric type return value, number of updated records can be fetched.
<update id="updateFinishedByTodIds">
UPDATE
t_todo
SET
finished = #{finished},
/* (2) */
version = version + 1
WHERE
/* (3) */
<foreach item="todoId" collection="todoIds"
open="todo_id IN (" separator="," close=")">
#{todoId}
</foreach>
</update>
</mapper>
- Repeats process for the list of IDs passed by argument, using foreach
element.
In the above example, IN clause is generated from the list of IDs passed by
argument.
Refer “MyBatis3 REFERENCE DOCUMENTATION (Dynamic SQL-
foreach-)” for details of foreach.
collection Specify collection for a process.
In the above example, the process is repeated for a list of IDs (todoIds)
of Repository method arguments.
item Specify local variable name that retains 1 element in the list.
separator Specify the string for separating elements in the list.
In the above example, ",", which is the separator character of IN clause,
is specified.
Note: Explanation hereafter shows an implementation example wherein an optimistic locking is performed
by using version column. However, it is not necessary to perform the processes related to optimistic locking
when optimistic locking is not required.
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
// (1)
boolean delete(Todo todo);
Note: ** Return value for the method that deletes a single Entity**
The return value of the method that deletes a single Entity can be boolean.
However, when the return value is to be handled as a data mismatch error due to multiple deletion results,
it is necessary to set numeric value type (int or long) as the return value and to check whether a single
deletion record exists. When main key is used as the delete condition, return value can be set to boolean
since, multiple deleted records are not obtained.
• When boolean is specified as return value, falseis returned when deleted records are 0 and true
is returned when deleted records are 1 or more.
• When numeric value type is specified as a return value, the number of deleted records is returned.
</mapper>
• Apply DI to the Repository in Service class and call the method for Repository interface.
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
import com.example.domain.repository.todo.TodoRepository;
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
// (3)
@Inject
TodoRepository todoRepository;
@Override
public Todo delete(String todoId, long version) {
// (4)
Todo currentTodo = todoRepository.findOne(todoId);
if (currentTodo == null || currentTodo.getVersion() != version) {
throw new ObjectOptimisticLockingFailureException(Todo.class, todoId);
}
// (5)
boolean deleted = todoRepository.delete(currentTodo);
// (6)
if (!deleted) {
return currentTodo;
}
Refer to “Using batch mode” for how to use JDBC batch update functionality.
The method to execute DELETE statement that concurrently deletes multiple records, is explained below.
package com.example.domain.repository.todo;
// (1)
int deleteOlderFinishedTodo(Date criteriaDate);
Note: Return value for the method that deletes Entity in batch
Return value for the method that deletes Entity in batch can be numeric value type (intor long). When
the return value is of numeric type, number of deleted records can be fetched.
</mapper>
MyBatis3 provides a mechanism through which dynamic SQL is built by using OGNL base expression (Expres-
sion language) and XML elements for building dynamic SQL.
choose Element that builds SQL by selecting one of the options from multiple
2.
options, that matches with the condition.
where Element that assigns or removes prefix and suffix for the built WHERE
3.
clause.
set Element that assigns or removes prefix or suffix for the built SET clause.
4.
bind Element that stores the results of OGNL expression in the variable.
6.
Variable stored by using bind variable can be referred in SQL.
Tip: Although it is not given in the list, trim element is provided as the XML element for building
dynamic SQL.
trim element is a more generalized XML element as compared to where element and set element.
In most of the cases, where element and set element can meet the requirements. Hence, description
of trim element is omitted in this guideline. Refer to “MyBatis3 REFERENCE DOCUMENTATION
(Dynamic SQL-trim, where, set-)” when it is necessary to use trim element.
Implementation of if element
if element is the XML element that builds SQL only when it matches with specified conditions.
SQL (WHERE clause) generated by dynamic SQL described above consists of 2 patterns.
choose element is the XML element for building SQL by selecting one option that matches the condition from
a set of conditions.
SQL (WHERE clause) that is generated by dynamic SQL described above consists of 2 patterns.
-- (1) createdAt!=null
...
WHERE
todo_title LIKE ? || '%' ESCAPE '~'
AND
created_at > ?
ORDER BY
todo_id
-- (2) createdAt==null
...
WHERE
todo_title LIKE ? || '%' ESCAPE '~'
AND
created_at > CURRENT_DATE
ORDER BY
todo_id
where element is the XML element for dynamically generating WHERE clause.
The SQL (WHERE clause) that is generated by dynamic SQL described above consists of 4 patterns as given
below.
todo_id
set element is the XML element for automatically generating SET clause.
foreach element is the XML element for repeating a process for a collection or an array.
finished,
created_at,
version
FROM
t_todo
<where>
<!-- (1) -->
<if test="list != null">
<!-- (2) -->
<foreach collection="list" item="date" separator="OR">
<![CDATA[
(created_at >= #{date} AND created_at < DATEADD('DAY', 1, #{date}))
]]>
</foreach>
</if>
</where>
ORDER BY
todo_id
</select>
Tip: foreach element consists of following attributes although they are not used in the above example.
close Specify the string that is set after processing the elements at the
2.
end of the collection.
index Specify the variable name that stores the loop number.
3.
There are only a few cases that use index attribute, however open attribute and close attribute are used
to generate IN clause etc. dynamically.
-- list=['accepted','checking']
...
AND order_status IN (?,?)
SQL (WHERE clause) generated by dynamic SQL described above consists of following 3 patterns.
-- (2) list=['2014-01-01']
...
FROM
t_todo
WHERE
(created_at >= ? AND created_at < DATEADD('DAY', 1, ?))
ORDER BY
todo_id
-- (3) list=['2014-01-01','2014-01-02']
...
FROM
t_todo
WHERE
(created_at >= ? AND created_at < DATEADD('DAY', 1, ?))
OR
(created_at >= ? AND created_at < DATEADD('DAY', 1, ?))
ORDER BY
todo_id
bind element is the XML element for storing OGNL expression result in the variable.
Tip: In the above example, although the variable created by using bind variable is specified as the bind
variable, it can also be used as substitution variable.
Refer to “SQL Injection countermeasures” for bind variable and substitution variable.
When performing LIKE search, the value to be used as search condition should be escaped for LIKE search.
The escape process for LIKE search can be implemented by using the method of
org.terasoluna.gfw.common.query.QueryEscapeUtils class provided by common library.
Refer to “Escaping during LIKE search” for specifications of the escape process provided by common library.
finished,
created_at,
version
FROM
t_todo
WHERE
/* (2) (3) */
todo_title LIKE #{todoTitleContainingCondition} ESCAPE '~'
ORDER BY
todo_id
</select>
Tip: In the above example, a method that performs the Escape process for partial match is called. However,
methods that perform the following processes are also provided.
Note: In the above example, the method that performs Escape process in the mapping file is called,
however Escape process can also be called as a Service process before calling the Repository method.
As role of component, it is appropriate that Escape process is performed in mapping file. Hence, in this
guideline, it is recommended to perform Escape process in mapping file.
It is important to take precautions when building SQL to avoid occurrence of SQL Injection.
MyBatis3 provides following two methods as the mechanism to embed values in SQL.
Warning: When the value entered by the user is embedded by using substitution variable, it should be
noted that the risk of SQL Injection is high.
When the value entered by the user needs to be embedded by using a substitution variable, the input
check must be performed in order to ensure that SQL Injection has not occurred.
Basically, it is strongly recommended not to use the value entered by the user as it is.
)
</insert>
• javaType
• jdbcType
• typeHandler
• numericScale
• mode
• resultMap
• jdbcTypeName
Basically, MyBatis simply selects an appropriate behavior just by specifying the property name. The at-
tributes described above can be specified when MyBatis does not select an appropriate behavior.
Warning:
Embedding value by a substitution variable must be used only after ensuring that the value
is safe for the application and by restricting its use to table name, column name and sort
conditions.
For example, the pair of code value and value to be embedded in SQL is stored in Map as shown below.
The value entered should be handled as code value and is expected to be converted to a safe value inside
the process executing the SQL.
In the above example, Map is used. However, “Codelist ” provided by common library can also be used.
If “Codelist ” is used, the value entered can be checked. Hence, the value can be safely embedded.
• projectName-domain/src/main/resources/META-INF/spring/projectName-codelist.xml
• Service class
@Inject
@Named("CL_DIRECTION")
CodeList directionCodeList;
// ...
In MyBatis3, SQL statement (or a part of SQL statement) can be shared by using sql element and include
element.
When pagination search is to be performed, WHERE clause of “SQL that fetches total records of Entity
matching with search conditions” and “SQL that fetches a list of Entities matching with search conditions”
should be shared.
todo_title,
finished,
created_at,
version
FROM
t_todo
<!-- (2) -->
<include refid="findPageByCriteriaWherePhrase"/>
ORDER BY
todo_id
</select>
Implementation of TypeHandler
When it is necessary to perform mapping with the Java class not supported by MyBatis3 standard and when it is
necessary to change the standard behavior of MyBatis3, a unique TypeHandler should be created.
How to implement the TypeHandler is explained using the examples given below.
A method added from JDBC 4.0 is used for the implementation of BLOB and CLOB.
When using a JDBC driver that is not compatible with JDBC 4.0 or a 3rd party wrapper class, it must
be noted that the operation may not work in the implementation example explained below. When the
operation is to be performed in an environment wherein the driver is not compatible with JDBC 4.0, the
implementation must be changed to suit the compatible version of JDBC driver to be used.
For example, a lot of methods added by JDBC 4.0 are not implemented in JDBC driver for PostgreSQL9.3
(postgresql-9.3-1102-jdbc41.jar).
MyBatis3 provides a TypeHandler for mapping BLOB in byte[]. However, when the data to be handled is
very large, it is necessary to map in java.io.InputStream.
package com.example.infra.mybatis.typehandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import java.io.InputStream;
import java.sql.*;
// (1)
public class BlobInputStreamTypeHandler extends BaseTypeHandler<InputStream> {
// (2)
@Override
public void setNonNullParameter(PreparedStatement ps, int i, InputStream parameter,
JdbcType jdbcType) throws SQLException {
ps.setBlob(i, parameter);
}
// (3)
@Override
public InputStream getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return toInputStream(rs.getBlob(columnName));
}
// (3)
@Override
public InputStream getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return toInputStream(rs.getBlob(columnIndex));
}
// (3)
@Override
public InputStream getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return toInputStream(cs.getBlob(columnIndex));
}
Since the fetched Blob can become null in case of the column which allows null,
4.
InputStream must be fetched only after performing null check.
In the implementation example described above, a private method is created since
same process is required for all three methods.
MyBatis3 provides a TypeHandler for mapping CLOB in java.lang.String. However, when the data to
be handled is very large, it is necessary to map it in java.io.Reader.
How to implement the TypeHandler for mapping CLOB in java.io.Readeris given below.
package com.example.infra.mybatis.typehandler;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.io.Reader;
import java.sql.*;
// (1)
public class ClobReaderTypeHandler extends BaseTypeHandler<Reader> {
// (2)
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Reader parameter,
JdbcType jdbcType) throws SQLException {
ps.setClob(i, parameter);
}
// (3)
@Override
public Reader getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return toReader(rs.getClob(columnName));
}
// (3)
@Override
public Reader getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return toReader(rs.getClob(columnIndex));
}
// (3)
@Override
public Reader getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return toReader(cs.getClob(columnIndex));
}
Since fetched Clob can become null in the column that allows null, Reader
4.
needs to be fetched only after performing null check.
In the implementation example described above, a private method is created since
same process is required for all three methods.
Note: Other classes provided by Joda-Time (LocalDateTime, LocalDate, LocalTime etc.) can
also be implemented in the same way.
package com.example.infra.mybatis.typehandler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.joda.time.DateTime;
// (1)
public class DateTimeTypeHandler extends BaseTypeHandler<DateTime> {
// (2)
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
DateTime parameter, JdbcType jdbcType) throws SQLException {
ps.setTimestamp(i, new Timestamp(parameter.getMillis()));
}
// (3)
@Override
public DateTime getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return toDateTime(rs.getTimestamp(columnName));
}
// (3)
@Override
public DateTime getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return toDateTime(rs.getTimestamp(columnIndex));
}
// (3)
@Override
public DateTime getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return toDateTime(cs.getTimestamp(columnIndex));
}
Since Timestampcan become null in the column that allows null, it needs to be
4.
converted to DateTime only after performing nullcheck.
In the implementation example described above, a private method is created since
same process is required for all three methods.
Implementation of ResultHandler
MyBatis3 provides a mechanism wherein search results are processed for each record.
are performed, the amount of memory consumed simultaneously can be restricted to a minimum.
For example, when the process is to be implemented wherein, search results are downloaded as data in CSV
format, it is advisable to process the search results per record.
Note: It is strongly recommended to use this mechanism when the quantity of search results may
be very large and when it is necessary to process the search result for each record at a time in Java
process.
When this mechanism of processing the search result for each record is not used, all the search result data,
“Size of one data record * number of search result records”, is stored in the memory at the same time, and
the data cannot be marked for GC till the process is completed for entire data.
In contrast, when a mechanism wherein the search results are processed one at a time, is used, only the
“size of one data record” is stored in the memory and that one data record can be marked for GC once the
process for that data record is completed.
For example, when “size of one data record” is 2KB and “number of search results” are 10,000 records,
the concurrent memory consumption is as below.
No particular issues have been observed in case of an application operated by a single thread. However,
problems may occur in case of an application like Web application that is operated by multiple threads.
If the process is performed for 100 threads at the same time, the concurrent memory consumption is as
below.
• When the process is performed collectively, depending on the maximum heap size specified, system
failure due to memory exhaustion and performance degradation due to frequent occurrence of full GC
are more likely to occur.
• When the process is performed per record, memory exhaustion or high-cost GC process can be con-
trolled.
Please note that the numbers used above are just the guidelines and not the actual measured values.
How to implement a process wherein the search results are downloaded as CSV data is given below.
// (1) (2)
void collectAllByCriteria(TodoCriteria criteria, ResultHandler<Todo> resultHandler);
@Inject
TodoRepository todoRepository;
// (4)
ResultHandler<Todo> handler = new ResultHandler<Todo>() {
@Override
public void handleResult(ResultContext<? extends Todo> context) {
Todo todo = context.getResultObject();
StringBuilder sb = new StringBuilder();
try {
sb.append(todo.getTodoId());
sb.append(",");
sb.append(todo.getTodoTitle());
sb.append(",");
sb.append(todo.isFinished());
sb.append(",");
sb.append(DATE_FORMATTER.print(todo.getCreatedAt().getTime()));
downloadWriter.write(sb.toString());
downloadWriter.newLine();
} catch (IOException e) {
throw new SystemException("e.xx.fw.9001", e);
}
}
};
// (5)
todoRepository.collectAllByCriteria(criteria, handler);
stop A method to notify MyBatis to stop the processing for the sub-
3.
sequent records. It is advisable to use this method when all the
subsequent records are to be deleted.
A method isStopped is also provided in ResultContext .However, its description is omitted since
it is used by MyBatis.
Following three types of modes are provided in MyBatis3 to execute SQL. Default is SIMPLE.
Here,
are explained.
Refer to “SQL execution mode settings” for the explanation of execution mode.
When the execution mode is changed from SIMPLE to ‘‘REUSE‘‘ mode, the handling of
PreparedStatement in MyBatis changes. However, there is no change in behavior (use method) of
MyBatis.
How to change the execution mode from default (SIMPLE) to REUSE is shown below.
When all the Update system methods of Mapper interface are to be called in a batch mode, execution mode can
be changed to BATCH mode using the method same as “Using PreparedStatement reuse mode“
However, since batch mode has a number of constraints, in actual application development, it is presumed to be
used in combination with SIMPLE or REUSE mode.
For example,
• Batch mode is used in the process wherein the highest priority is to meet performance requirements asso-
ciated with update of large amount of data.
• SIMPLE or REUSE mode is used for a process wherein it is necessary to determine the update results to
maintain data consistency such as in optimistic locking control etc.
Hereafter,
are explained.
Settings for creating an individual batch mode Repository When a batch mode Repository
is to be created for a specific Repository, a Bean can be defined for the Repository by using
org.mybatis.spring.mapper.MapperFactoryBean provided by MyBatis-Spring.
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation"
value="classpath:META-INF/mybatis/mybatis-config.xml"/>
</bean>
<mybatis:scan base-package="com.example.domain.repository"
template-ref="sqlSessionTemplate"/> <!-- (2) -->
</beans>
Note: If a Bean is defined for SqlSessionTemplate, following WARN log is output when the appli-
cation is terminated.
This is because close method is called while terminating ApplicationContext of Spring since
‘‘java.io.Closeable‘‘ is inherited by SqlSession interface.
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:
at java.lang.reflect.Method.invoke(Method.java:483) ~[na:1.8.0_20]
If there are no specific issues related to system operation, this measure is not required since it does not
affect the application behavior even after the log is output.
However, in case of any system operation issues like log monitoring etc, log output can be controlled by
specifying the method (destroy-method attribute) that is called while terminating Spring Application-
Context.
<bean id="batchSqlSessionTemplate"
class="org.mybatis.spring.SqlSessionTemplate"
destroy-method="getExecutorType">
<constructor-arg index="0" ref="sqlSessionFactory"/>
<constructor-arg index="1" value="BATCH"/>
</bean>
Settings for creating Batch mode Repository in batch When a batch mode Repository is to be created in a
batch, a Bean can be defined for the Repository by using the scan function (mybatis:scan element) provided
by MyBatis-Spring.
In the configuration example below, Bean is registered for the Repositories of REUSE mode and BATCH mode,
with respect to all the Repositories.
• Create BeanNameGenerator.
package com.example.domain.repository;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.util.ClassUtils;
import java.beans.Introspector;
// (1)
public class BachRepositoryBeanNameGenerator implements BeanNameGenerator {
// (2)
@Override
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation"
value="classpath:META-INF/mybatis/mybatis-config.xml"/>
</bean>
<bean id="batchSqlSessionTemplate"
class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
<constructor-arg index="1" value="BATCH"/>
</bean>
</beans>
How to use the Repository of batch mode An implementation example on how to access database by using
batch mode Repository is given below.
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
// (1)
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void updateTodos(List<Todo> todos) {
for (Todo todo : todos) {
// (2)
todoBatchRepository.update(todo);
}
}
Call the method for batch mode Repository and update Entity.
2.
In case of batch mode Repository, since the SQL is not executed in the timing when
method is called. As a result, the update results returned from method need to be
ignored.
The SQL for updating an Entity is executed in a batch immediately before committing
a transaction and the transaction is committed if there is no error.
Notes of sequence of calling repository method refer to “Sequence of calling Repository method”.
It is important to note the following points in implementation of Service class when using batch mode Repository.
Determination of update results When batch mode Repository is used, the validity of update results cannot
be checked.
Update results returned from Mapper interface method when a batch mode is used, are as follows.
• Fixed value(org.apache.ibatis.executor.BatchExecutor#BATCH_UPDATE_RETURN_VALUE)
when return value is numeric (int or long)
This is due to the mechanism wherein SQL is not executed within the timing of calling Mapper interface method
but is queued for batch execution (java.sql.Statement#addBatch()).
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void updateTodos(List<Todo> todos) {
for (Todo todo : todos) {
boolean updateSuccess = todoBatchRepository.update(todo);
// (1)
if (!updateSuccess) {
// ...
}
}
}
Based on the application requirement, it is also necessary to check the validity of update results executed in the
batch. In such cases, “a method to execute SQL queued for batch execution” is provided in the Mapper interface.”
Warning: Regarding update results returned by JDBC driver while using batch mode
Although it has been described earlier that update results at the time of batch execution can be re-
ceived when a method which assigns @Flush annotation (and flushStatements method of
SqlSession interface) is used, it cannot be guaranteed that the update results returned from JDBC
driver can be used as “number of processed records”.
Since it depends on implementation of JDBC driver to be used, the specifications of JDBC driver to be
used must be checked in advance.
How to create and call a method which assigns @Flush annotation is given below. |
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void updateTodos(List<Todo> todos) {
How to detect the unique constraint violation When batch mode Repository is used, it is not possible to
detect database errors like unique constraint violation etc. as a Service process.
This is due to the mechanism wherein SQL is not executed within the timing of calling a Mapper interface method
and is queued for the batch execution (java.sql.Statement#addBatch()). It signifies that the imple-
mentation given below cannot be performed.
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void storeTodos(List<Todo> todos) {
for (Todo todo : todos) {
try {
todoBatchRepository.create(todo);
// (1)
} catch (DuplicateKeyException e) {
// ....
}
}
}
Depending on application requirement, it is necessary to detect a unique constraint violation at the time of batch
execution. In such cases, “a method to execute SQL queued for batch execution (@Flush method)” must be
provided in Mapper interface. Refer “Determination of update results” described earlier for details of @Flush
method.
Sequence of calling Repository method Batch mode is used to improve the performance of update process.
However, if the calling sequence of Repository method is incorrect, no improvement is observed in the perfor-
mance.
It is important to understand the MyBatis specifications given below, in order to improve the performance using
batch mode.
• When query (SELECT) is executed, SQL waiting in the queue till then is executed in batch.
• PreparedStatement is generated for each update process (Repository method) called in succession
and SQL is queued.
It signifies that if the implementation is performed as below, advantages of using batch mode cannot be obtained.
• Example 1
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void storeTodos(List<Todo> todos) {
for (Todo todo : todos) {
// (1)
Todo currentTodo = todoBatchRepository.findOne(todo.getTodoId());
if (currentTodo == null) {
todoBatchRepository.create(todo);
} else{
todoBatchRepository.update(todo);
}
}
}
• Example 2
@Transactional
@Service
public class TodoServiceImpl implements TodoService {
@Inject
@Named("todoBatchRepository")
TodoRepository todoBatchRepository;
@Override
public void storeTodos(List<Todo> todos) {
for (Todo todo : todos) {
// (2)
todoBatchRepository.create(todo);
todoBatchRepository.createHistory(todo);
}
}
How to call a stored procedure or function registered in database from MyBatis3 is explained.
/* (1) */
CREATE FUNCTION findTodo(pTodoId CHAR)
RETURNS TABLE(
todo_id CHAR,
todo_title VARCHAR,
finished BOOLEAN,
created_at TIMESTAMP,
version BIGINT
) AS $$ BEGIN RETURN QUERY
SELECT
t.todo_id,
t.todo_title,
t.finished,
t.created_at,
t.version
FROM
t_todo t
WHERE
t.todo_id = pTodoId;
END;
$$ LANGUAGE plpgsql;
// (2)
public interface TodoRepository extends Repository {
Todo findOne(String todoId);
}
</mapper>
6.2.4 Appendix
When using a Mapper interface, the developer can execute SQL only by creating a Mapper interface and
mapping file.
when executing an application, implementation class of Mapper interface is generated by MyBatis3 using the
Proxy function of JDK. Hence, the developer need not create an implementation class of Mapper interface.
It is not necessary to define inheritance and annotation of interface provided by MyBatis3, for the Mapper
interface and it can be simply created as a Java interface.
Example of how to create a Mapper interface and mapping file, and example of how to use it in the application
(Service) are given below.
Here, the focus should be on the main points related to explanation of code since the aim is to form an image of
the deliverables to be created by developers.
In this guideline, since it is presumed that that the Mapper interface of MyBatis3 is used as Repository
interface, interface name is in the format, “Entity name” + "Repository" .
package com.example.domain.repository.todo;
import com.example.domain.model.Todo;
In the mapping file, FQCN (fully qualified class name) of Mapper interface is specified as namespace. Its
association with SQL that is executed while calling the method defined in the Mapper interface can be
formed by specifying a method name in id attribute of various statement tags (insert/update/delete/select
tags).
</mapper>
When a method of Mapper interface is to be called from an application (Service), a method of Mapper
object injected by Spring (DI container) is called. The application (Service) transparently executes SQL by
calling Mapper object method and can obtain SQL execution results.
package com.example.domain.service.todo;
import com.example.domain.model.Todo;
import com.example.domain.repository.todo.TodoRepository;
@Inject
TodoRepository todoRepository;
The process flow up to SQL execution when Mapper interface method is called, is shown below.
SqlSession sets the value in bind variable specified in the SQL statement fetched
6.
by mapping file and executes SQL.
Tip: Statement ID
Statement ID is a key to specify the SQL statement to be executed. It is generated in accordance with the
rule “FQCN of Mapper interface + ”.” + name of the called Mapper interface method”.
TypeAlias settings
TypeAlias should basically be configured per package by using package element. However, following methods
can also be used.
• A method to overwrite the alias name which is assigned as default (a method that specifies an optional alias
name)
• projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml
<typeAliases>
<!-- (1) -->
<typeAlias
type="com.example.domain.repository.account.AccountSearchCriteria" />
<package name="com.example.domain.model" />
</typeAliases>
When alias is set by using package element or when alias is set by omitting alias attribute of typeAlias
element, alias for TypeAlias is a part obtained after removing the package part from fully qualified class name
(FQCN).
When optional alias is to be used instead of default alias, it can be specified by specifying
@org.apache.ibatis.type.Alias annotation in the class wherein TypeAlias to is to be configured.
package com.example.domain.model.book;
@Alias("BookAuthor") // (1)
public class Author {
// ...
}
package com.example.domain.model.article;
@Alias("ArticleAuthor") // (1)
public class Author {
// ...
}
Tip: An alias name for TypeAlias is applied in the following priority order.
• Alias name assigned as default (part after removing the package name from fully qualified class name)
This mechanism is effective when building an application that can support multiple databases as operating envi-
ronment.
Note: In this guideline, it is recommended to manage the components and configuration file that are
dependent on the environment by a sub project called [projectName]-env and create components and con-
figuration file that are in execution environment at the time of building.
• Commercial environment
It can also be used in the development of an application that supports multiple databases.
Architects should try to achieve a uniform implementation for overall application by clearly identi-
fying a guideline on how to implement SQL environment dependency based on the differences in the
database.
<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- (3) -->
<property name="databaseIdProvider" ref="databaseIdProvider"/>
<property name="configLocation"
value="classpath:/META-INF/mybatis/mybatis-config.xml" />
</bean>
</beans>
Note: In this guideline, it is recommended to use a method to map the product name of database and
database ID, by specifying properties property
This is due to possible change in the product name of database fetched from JDBC driver, based on the
JDBC version.
When properties property is used, the difference between product names of each version to be used
can be managed at a single location.
INSERT INTO
t_todo
(
todo_id
,todo_title
,finished
,created_at
,version
)
VALUES
(
#{todoId}
,#{todoTitle}
,#{finished}
,#{createdAt}
,#{version}
)
</insert>
Tip: In the above example, UUID_GENERATE_V4() is called as the UUID generation function for
PostgreSQL. However, this function belongs to a sub-module called uuid-ossp.
Tip: Database ID can also be referred in OGNL base expression (Expression language).
It signifies that database ID can be used as a condition for dynamic SQL. How to implement is given below.
<bind name="criteriaDate"
value="'DATEADD(\'DAY\',#{days} * -1,#{currentDate})'"/>
</when>
<when test="_databaseId == 'postgresql'">
<bind name="criteriaDate"
value="'#{currentDate}::DATE - (#{days} * INTERVAL \'1 DAY\')'"/>
</when>
</choose>
<![CDATA[
created_at < ${criteriaDate}
]]>
</select>
How to fetch a main Entity and a related Entity by a single SQL is explained below.
When a mechanism that fetches a main Entity and a related Entity together is to be used, it is not necessary to
build an Entity (JavaBean) in the Service class and Service class can solely focus on implementation of business
logic (business rules).
Warning: It is important to note the following points when main Entity and related Entity are to be
fetched together.
• In the explanation below, all the related Entities are fetched together by a single SQL. However,
when it is to be used in the actual project, only the related Entities that are required in the
process should be fetched. If unused related Entities are fetched together, it results in generation
of unnecessary objects and as mapping process is performed, it causes efficiency deterioration.
Particularly, in the SQL that performs list search, in many cases, only the required related
Entities are fetched.
• For the related Entities which are used less frequently, the entities can be fetched independently
without fetching them together.
If the related Entities that are used less frequently are fetched simultaneously, unnecessary ob-
jects are generated and as mapping process is performed, it can cause efficiency deterioration.
• When multiple related Entities with 1:N relation are to be included, it is advisable to adopt
the method wherein the main Entity and related Entity are fetched separately. When there are
multiple related Entities with 1:N relation, it becomes necessary to fetch unnecessary data from
DB causing deterioration inefficiency. Refer to “How to resolve N+1” for how to fetch main
Entity and related Entity separately.
Tip: Following methods are provided when it is necessary to separately fetch the related Entities that are
used less frequently.
• By calling a method (SQL) that fetches a related Entity in the process of Service class.
• By marking the related Entity as “Lazy Load” and transparently executing the SQL when Getter
method is called.
When “Lazy Load” mechanism is used, it is not necessary to build an Entity (JavaBean) in Service class
and Service class can solely focus on implementation of business logic (business rules).
When “Lazy Load” is used in SQL that performs list search, N+1 can occur. Hence adequate care
must be taken.
Refer to “Settings to apply Lazy Load for related Entity” for how to use “Lazy Load”.
An implementation example is explained below wherein order data handled by a shopping site is fetched together
in a single SQL and mapped in main Entity and related Entity.
The implementation method explained here is just an example. MyBatis3 provides a lot of functions that are not
explained in this section and can perform a much higher level of mapping.
Refer to “MyBatis3 REFERENCE DOCUMENTATION(Mapper XML Files-Result Maps-) ” for details of map-
ping function of MyBatis3.
SQL (DDL and DML) for creating the storage data and the table layout used in the explanation are as follows.
(SQL is for H2 Database)
COMMIT;
In the implementation example, the records stored in the table above are mapped in the following Entity (Jav-
aBean).
package com.example.domain.repository.order;
import com.example.domain.model.Order;
import java.util.List;
SQL implementation
When related Entities are to be fetched together by a single SQL, JOIN is performed on the tables to be fetched
and all the records required for mapping are fetched.
i.code AS item_code,
i.name AS item_name,
i.price AS item_price,
/* (5) */
ct.code AS category_code,
ct.name AS category_name,
/* (6) */
cp.code AS coupon_code,
cp.name AS coupon_name,
cp.price AS coupon_price
FROM
${orderTable} o
/* (7) */
INNER JOIN c_order_status os ON os.code = o.status_code
INNER JOIN t_order_item oi ON oi.order_id = o.id
INNER JOIN m_item i ON i.code = oi.item_code
INNER JOIN m_item_category ic ON ic.item_code = i.code
INNER JOIN m_category ct ON ct.code = ic.category_code
/* (8) */
LEFT JOIN t_order_coupon oc ON oc.order_id = o.id
LEFT JOIN m_coupon cp ON cp.code = oc.coupon_code
</sql>
item_code ASC,
category_code ASC,
coupon_code ASC
</select>
</mapper>
Perform Outer JOIN for the table wherein records might not be stored. When
8.
coupon is not used, it is necessary to perform Outer JOIN since records are not
stored in t_order_coupon. This is also applicable for t_coupon to be joined with
t_order_coupon.
Implement the SQL for findOne method.
9.
Specify alignment sequence of the Entity with 1:N relation, in ORDER BY clause. In
the above example, sorting is done in the ascending order of PK.
Implement SQL for findPage method. Specify alignment sequence of Entity that
10.
has a 1:N relation with Order, in ORDER BY clause. In the above example, Order is
sorted in descending sequence of PK (new sequence) and related Entity is sorted in
the ascending order of PK.
Tip: When it is necessary to perform a pagination search while fetching related Entities with 1:N relation
together by a single SQL, RowBounds provided by MyBatis3 cannot be used.
• At first, a method is called which searches only main Entity and related Entities are then fetched by
calling a separate method.
• A virtual table that stores only main Entity within the page range is created by SQL and all the records
necessary for mapping are fetched by performing JOIN on the records of virtual table (findPage
shown in the above example is implemented using this pattern)
If SQL (findPage) above is executed, following records are fetched. The order records are 2. However, a total of
9 records are fetched since the records are joined with a related table that stores multiple records.
The breakdown is
• 1st to 3rd rows are records for generating Order object with order ID 2
• 4th to 9th rows are records for generating Order object with order ID 1
How to map search results (ResultSet) to JavaBean is explained below using a record wherein order ID is 1.
Implementation of mapping
The definition for mapping the records described above in the Order object and related Entity is given below.
</mapper>
Note: For creating Entity class of “Implementation of Entity”, “Code table is handled as a basic type of
java.lang.String rather than handling as an Entity.”. This is because the data stored in the code table uses a
different mechanism like “Codelist” etc.
The purpose of this section is to explain how to map to a related Entity (JavaBean), hence the explanation
on code table also being handled as an Entity is added.
In the actual project, it is recommended to create an Entity based on the policies for creating an Entity class.
Note: The value of records that are grouped in id column is set in OrderStatus object.
Note: In OrderItem object, values of records that are grouped in id column and item_code column
are set.
</collection>
</resultMap>
Note: In Item object, values of records that are grouped in id column and item_code column are set.
Note: In Category object, values of records that are grouped in id column, item_code column and
category_code column are set.
Note: In Coupon object, values of records that are grouped in id column and coupon_code column
are set.
Object diagram after mapping The status of Order object and related Entities that are actually mapped is as
given below.
Warning: It should be noted that when the records with 1:N relationship are mapped using JOINs,
fetching the data of grayed out part becomes unnecessary.
If the same SQL is used in a process that does not use data from the N part, it results in fetching of
unnecessary data. Therefore, two separate SQLs, one that fetches the N part and another that does not
fetch the N part should be created.
MyBatis3 provides a method wherein a related Entity is fetched at the time of mapping by using a different SQL
(nested SQL).
When a related Entity is fetched by using a nested SQL, following can be performed easily.
Warning: It should be noted that even if the definitions are simplified, if nested SQL is used exten-
sively, it can cause N+1.
Default MyBatis3 behavior while using nested SQL is “Eager Load”. It signifies that SQL is executed
regardless of whether related Entities are used and the following risks are more likely to occur.
• Unnecessary SQL execution and data fetching
• N+1
Tip: MyBatis3 provides an option to change the behavior to “Lazy Load” while fetching a related Entity
using a nested SQL.
Refer to “Settings to apply Lazy Load for related Entity” for how to use “Lazy Load”.
Implementation example wherein a related Entity is fetched by using a nested SQL is shown below.
<select id="findAllCategoryByItemCode"
parameterType="string" resultType="Category">
SELECT
ct.code,
ct.name
FROM
m_item_category ic
INNER JOIN m_category ct ON ct.code = ic.category_code
WHERE
ic.item_code = #{itemCode}
ORDER BY
code
</select>
Note: In the above example, since fetchType attribute is not specified, whether to use “Lazy Load” or
“Eager Load” depends on configuration of overall application.
For configuration of overall application, refer to “MyBatis settings for using Lazy Load”.
Although default behavior of MyBatis3 while fetching a related Entity by using a nested SQL is “Eager Load”,
“Lazy Load” can also be used.
The minimum necessary settings for using “Lazy Load” and how to use “Lazy Load” are explained below.
For the configuration values that are not explained, refer to “MyBatis3 REFERENCE DOCUMENTA-
TION(Mapper XML Files-settings-)” .
Adding a Bytecode Manipulation Library When “Lazy Load” is used, one of the libraries given below is
necessary in order to generate a Proxy object for implementing “Lazy Load”.
• JAVASSIST
• CGLIB
CGLIB had been used as default until MyBatis 3.2 series. From MyBatis 3.3.0, JAVASSIST has been used as
default at the MyBatis 3.3.0 or later version, which is supported by terasoluna-gfw-mybatis3 5.2.0.RELEASE. In
addition, it is possible to use “Lazy Load” without adding the library because the JAVASSIST is embedded in the
MyBatis since MyBatis 3.3.0.
MyBatis settings for using Lazy Load In MyBatis3, availability of “Lazy Load” can be specified in the 2
locations given below.
• Individual settings are specified in fetchType attribute of association element and collection
element of mapping file.
<select id="findAllCategoryByItemCode"
parameterType="string" resultType="Category">
SELECT
ct.code,
ct.name
FROM
m_item_category ic
INNER JOIN m_category ct ON ct.code = ic.category_code
WHERE
ic.item_code = #{itemCode}
ORDER BY
code
</select>
Settings for controlling execution timing of Lazy Load MyBatis3 provides an option to control the timing in
which “Lazy Load” is executed.
The settings to control the timing in which “Lazy Load” is executed is specified in MyBatis configuration file
(projectName-domain/src/main/resources/META-INF/mybatis/mybatis-config.xml).
Warning: In case of “true” (default), exercise caution since SQL is likely to be executed for fetching
the unused data.
Basically, there is a case wherein the following mapping is carried out and only the property for which
“Lazy load” is not to be performed, is accessed. In case of “true” (default), “Lazy Load” is executed
even without directly accessing the property for which “Lazy Load” is to be performed.
It is recommended to set aggressiveLazyLoading to “false unless there is a specific reason.
• Entity
• Mapping file
Todo
TBD
• persistence.xml settings
• Examples of cases wherein it is desirable to change the default setting values for specifying fetch method
of related-entities.
6.3.1 Overview
This section explains the method to access the database using JPA.
In this guideline, it is assumed that Hibernate is used as JPA provider and Spring Data JPA as JPA wrapper.
Warning: The contents described in this chapter may not be applicable for JPA providers other than Hibernate
such as EclipseLink etc.
About JPA
1. a way of mapping the records in a relational database, with the java objects
2. a mechanism for reflecting the operations done on the java object, to the records in a relational
database.
Mapping of Java objects to the relational database records at the time of using JPA is as follows:
In JPA, if the value stored in the “managed” entity is changed (by calling setter method), there is a mechanism to
reflect the changes in the relational database.
This mechanism is quite similar to the client software such as Table viewer with Edit functionality.
In client software such as Table viewer, if the value of viewer is changed, it is reflected in the database. While in
JPA, if the value of Java object (JavaBean) called “Entity” is changed, it is reflected in the database.
1.
Entity class A Java class representing the records in the relational database
The class with @javax.persistence.Entity annotation is an
Entity class.
2.
EntityManager An interface which provides API necessary for managing the life cycle of
entity
Using methods of javax.persistence.EntityManager, the
application handles the relational database records as Java objects.
When using Spring Data JPA, this interface is usually not used directly;
however, if it is necessary to generate a query that cannot be expressed
using the Spring Data JPA mechanism, then this interface can be used to
fetch the entity.
3.
TypedQuery An interface which provides API for searching an entity
Using methods of javax.persistence.TypedQuery, the
application searches for the entity matching the specified conditions other
than ID.
1320 6 Data Access
When using Spring Data JPA, this interface is usually not used directly;
however, if it is necessary to generate a query that cannot be expressed
using Spring Data JPA mechanism, then this interface can be used to
search the entity.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) When persist method of EntityManager is called, the entity (“New” entity) passed as an
argument is stored in PersistenceContext as “managed” entity.
(2) When find method of EntityManager is called, a “managed” entity with ID passed as an
argument, is returned.
If it does not exist in PersistenceContext, the records to be mapped are retrieved from the
relational database by executing a query and stored as “managed” entity.
(3) When merge method of EntityManager is called, the state of the entity (“detached”) passed
as an argument, is merged with “managed” entity.
If it does not exist in PersistenceContext, the records to be mapped are retrieved from the
relational database by executing a query. The state of the entity passed as an argument is
merged after the “managed” entity is stored.
Note that when this method is called, the entity passed as an argument does not necessarily get
stored as “managed” entity unlike persist method.
(4) When remove method of EntityManager is called, the “managed” entity passed as an
argument becomes “removed” entity.
If this method is called, it is not possible to retrieve the “removed” entity.
(5) When flush method of EntityManager is called, the operations of the entity accumulated
using persist, merge and remove methods are reflected in the relational database.
By calling this method, the changes done for an entity are synchronized with the records of
relational database.
However, the changes made only for the records of relational database are not synchronized
with the entity.
If the entity is searched by executing a query without using find method of EntityManager,
then prior to the search process, a process similar to flush method is executed in the internal
logic of EntityManager and the operations of the accumulated entity are reflected in the
relational database.
For timing to reflect the persistence operations at the time of using Spring Data JPA,
refer to
The detach method, refresh method and clear method are available in EntityManager to manage the
entity life cycle. However, when using Spring Data JPA, there is no mechanism to call these methods using
the default function, hence only their roles are described below.
• refresh method is used to update the “managed” entity as per the state of relational database.
• clear method is used to delete the entity managed in PersistenceContext and the accumulated opera-
tions from the memory.
clear method can be called by setting the clearAutomatically attribute of @Modifying annotation of
Spring Data JPA to true. For details, refer to Operating the entities of Persistence Layer directly.
The operations performed on “new” and “detached” entities are not reflected in the relational database
unless persist method or merge method is called.
Spring Data JPA provides the library to create Repository using JPA.
If Spring Data JPA is used, it is possible to retrieve an entity that matches the specified conditions only by
defining the
query method in the Repository interface; hence the amount of implementation for performing the entity
operations can be reduced.
However, only static query which can be expressed using annotation, can be defined in query method; hence
it is necessary to implement custom Repository class for the query such as dynamic query which cannot be
expressed using annotation.
The basic flow at the time of accessing the database using Spring Data JPA is shown below.
(2) Proxy class which dynamically implements Repository interface, delegates the process to
org.springframework.data.jpa.repository.support.SimpleJpaRepository
or custom Repository class.
Parameters specified by Service are passed.
1324 (3) Repository implementation class calls JPA APIs. 6 Data Access
The parameters specified by Service and the parameters generated by implementation class of
Repository are passed.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When creating the repository using Spring Data JPA, APIs of JPA need not be called directly; however, it is
better to know which JPA method is being called by
methods of Repository interface of Spring Data JPA.
The JPA methods called by main methods of Repository interface of Spring Data JPA are shown below.
pom.xml settings
When using JPA (Spring Data JPA) in infrastructure layer, add the following dependency to pom.xml
Application Settings
Datasource settings
EntityManager settings
• xxx-infra.xml
Set the SQL output flag. In the example, “false: Do not output” has been specified.
(2)
(3) Set the value corresponding to RDBMS to be used. It is possible to specify the value defined in
org.springframework.orm.jpa.vendor.Database enumerator type.
In the example, “PostgreSQL” has been specified.
[The value should be changed according to the database used in the project]
If the database to be used changes with the environment, the value should be defined in
properties file.
(6) Specify the datasource to be used for accessing persistence layer (DB).
1328 (8) Specify the settings to configure EntityManager of Hibernate. 6 Data Access
For details, refer to “Hibernate Reference Documentation” .
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Tip: When using the Oracle database, ANSI standard SQL JOIN for combining tables, can be used by
specifying the following settings in jpaPropertyMap mentioned in (8).
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- omitted -->
<property name="jpaPropertyMap">
<util:map>
<!-- omitted -->
<entry key="hibernate.dialect"
value="org.hibernate.dialect.Oracle10gDialect" /> <!-- (9) -->
</util:map>
</property>
</bean>
Perform the following settings when transaction manager (JTA) of the application server is to be used.
The difference with the case wherein JTA is not used, is explained below.
For other locations, same settings as the case wherein JTA is not used can be performed.
• xxx-infra.xml
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaPropertyMap">
<util:map>
</util:map>
</property>
</bean>
(10) Specify the datasource to be used for accessing persistence layer (DB).
When using JTA, specify the DataSource defined in application server in "jtaDataSource"
property and not in "dataSource" property.
Refer to Datasource settings of common edition for the method to fetch DataSource defined in
application server.
Note: When it is necessary to switch the transaction manager to be used as per the environment, then
it is recommended that you define "entityManagerFactory" bean in xxx-env.xml instead of
xxx-infra.xml.
An example wherein it is necessary to change the transaction manager as per environment can be: use of
application server without JTA function such as Tomcat in case of local environment, and use of applica-
tion server with JTA function such as Weblogic in case of production environment as well as various test
environments.
PlatformTransactionManager settings
• xxx-env.xml
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"> <!-- (1) -->
Sr. Description
No.
Specify org.springframework.orm.jpa.JpaTransactionManager. This class con-
trols transaction by calling APIs of JPA.
(1)
Perform the following settings when transaction manager (JTA) of the application server is to be used.
• xxx-env.xml
Sr. Description
No.
The most appropriate org.springframework.transaction.jta.JtaTransactionManager
is defined as bean with id as “transactionManager”, in the application server on which the application
(1)
has been deployed. This class controls the transaction by calling JTA APIs.
persistence.xml settings
Todo
TBD
Currently, there are no mandatory settings to be performed in persistence.xml; however such need
may arise in future.
When using EntityManagerFactory in application server of Java EE, it may be necessary to perform
few settings in persistence.xml ; hence we are planning to provide maintenance for such settings
in future.
• xxx-infra.xml
</beans>
Sr. Description
No.
(1) Import schema definition for Spring Data JPA configuration and assign ("jpa") as namespace.
(2) Specify base package wherein Repository interface and custom Repository class are stored.
Interface inheriting org.springframework.data.repository.Repository and
interface with org.springframework.data.repository.RepositoryDefinition
annotation are automatically defined as a bean of Repository class of Spring Data JPA.
transaction-manager-
2.
ref
Specify PlatformTransactionManager to be used when the methods
of Repository are called.
The bean registered with "transactionManager" bean name is used by
default.
It needs to be specified when the bean name of
PlatformTransactionManager to be used is not
"transactionManager".
named-queries-
3.
location
Specify the location of Spring Data JPA properties file wherein Named Query
is specified.
“classpath:META-INF/jpa-named-queries.properties” is used by default.
query-lookup-
4.
strategy
Specify the method to Lookup the query to be executed when query method is
called.
By default, it is "CREATE_IF_NOT_FOUND". For details, refer to Spring
Data Commons - Reference Documentation の “Query lookup strategies”. Use
the default settings if there is no specific reason.
factory-class
5.
Specify Factory for generating class to implement the process when the
method of Repository interface is called.
org.springframework.data.jpa.repository.support.JpaRepositor
is used by default. Specify the Factory created for changing default
implementation of Spring Data JPA or for adding a new method.
For how to add a new method, refer to Adding the custom methods to all
Repository interfaces in batch.
repository-impl-
6.
postfix
Specify suffix indicating that it is an implementation class of custom
6.3. Database Access (JPA) 1333
Repository.
By default, it is "Impl". For example: when Repository interface name is
OrderRepository, OrderRepositoryImpl will be the
implementation class of custom Repository. Use the default settings if there is
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
OpenEntityManagerInViewInterceptor settings
To perform Lazy Fetching of Entity in application layer such as Controller and JSP
etc., the lifetime of EntityManager should be extended till application layer using
org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor.
Considering the following perspectives, it is recommended that you use Lazy Fetch as fetch method and
OpenEntityManagerInViewInterceptor.
• Fetching as a process of Service class leads to insignificant implementation such as calling only getter
method or accessing the collection fetched by calling getter method.
• When Eager Fetch is used, it is likely that the data which is not used in application layer is also fetched
impacting the performance.
• spring-mvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /> <!-- (1) -->
<mvc:exclude-mapping path="/resources/**" /> <!-- (1) -->
<mvc:exclude-mapping path="/**/*.html" /> <!-- (1) -->
<!-- (2) -->
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
Sr. Description
No.
(1) Specify the path for which interceptor is to be applied and path for which interceptor is not to be
applied.
In this example, interceptor is being applied for paths other than paths of resource files (js, css,
image etc.) and static web page (HTML).
(2) Specify
org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor.
It is recommended that interceptor not be applied to the path of static resources (js, css, image, html etc.),
as there is no data access in such cases. Application of interceptor to the path of static resources leads to
execution of unnecessary processes (such as instance generation and close process).
When Lazy Fetch is required in Servlet Filter, it is necessary to extend the lifetime of EntityManager till the
Servlet Filter layer using
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.
For example, this case is applicable when
org.springframework.security.core.userdetails.UserDetailsService of
SpringSecurity is inherited and if Entity object is accessed in the inherited logic.
However, if Lazy Fetch is not required, there is no need to extend the lifetime of EntityManager till the
Servlet Filter layer.
It is recommended that you design and implement such that Lazy Fetch does not occur in Servlet
Filter layer. If OpenEntityManagerInViewInterceptor is used, it is possible to specify the
applicable and non-applicable URL patterns; thus the path for which lifetime of “EntityManager” is to be
extended till application layer can also be easily specified. For the data access required in Servlet Filter, the
data should either be fetched in advance in Service class or should be loaded in advance using Eager Fetch;
thereby, avoiding the occurrence of Lazy Fetch.
• web.xml
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-c
</filter>
<!-- (2) -->
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Sr. Description
No.
(1) Specify
org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.
This Servlet Filter needs to be defined before the Servlet Filter in which Lazy Fetch occurs.
(2) Specify the pattern of the URL for which filter is to be applied. It is recommended that you apply
the filter only to the required path; however, if the settings are complicated, you can also specify
“/*” (All Requests).
Spring Data provides the following 3 methods to create entity specific Repository interface.
The method to create entity specific Repository interface by inheriting from the interface of Spring Data is ex-
plained below.
org.springframework.data.jpa.repository
3.
JpaRepository
Repository interface that provides JPA specifications
dependent methods.
PagingAndSortingRepository is inherited; hence
methods of PagingAndSortingRepository and
CrudRepository can also be used.
If there is no specific reason, it is recommended that
you create entity specific Repository interface by
inheriting this interface.
(1) Inherit JpaRepository and specify entity type in generic type <T> and entity ID type in
generic type <ID extends Serializable>.
In the above example, Order type is specified in entity and Integer type in entity ID.
If entity specific Repository interface is created by inheriting JpaRepository, then the following methods can
be implemented.
T saveAndFlush(T entity)
3.
Once the persistence operations for the specified entity are
accumulated in EntityManager, this method reflects
the accumulated persistence operations
(INSERT/UPDATE/DELETE) in persistence layer (DB).
For the entity managed under EntityManager, accumulated persistence operations are executed just
before committing a transaction and reflected in persistence layer (DB). Therefore, in order to handle errors
such as unique constraint violation in transaction management (Service processing), it is necessary to call
“saveAndFlush” method or “flush” method and execute persistence operations for the Entity accumulated
in “EntityManager” forcibly. If only an error is to be notified to the client, it is OK to perform exception
handling in Controller and set an appropriate message.
saveAndFlush and flush are JPA dependent methods, hence do not use these methods if there is no
specific purpose.
• Normal flow
• flush flow
When the following method is called, in order to avoid inconsistency between the data managed in
EntityManager and persistence layer (DB), the persistence operations of the entity accumulated in
EntityManager are reflected in the persistence layer (DB) before the main process is carried out.
• long count()
In case of above methods, query is executed directly in the persistence layer (DB); hence inconsistency may
occur unless the operations are reflected in the persistence layer (DB) before the main process is carried
out. Calling of query methods described later also triggers the reflection of persistence operations for the
entity accumulated in EntityManager, in the persistence layer (DB).
Inheriting a common project specific interface in which only the required methods are defined
Amongst the methods defined in interface of Spring Data, this section defines the method to create entity specific
Repository interface by creating and inheriting a common project specific interface in which only the required
methods are defined.
The signature of methods should match with the methods of Repository interface of Spring Data;
Amongst the methods of Repository interface of Spring Data, there are methods which are not used
or which are not desirable to be used in the actual application. In order to remove such methods
from Repository interface, refer below. The methods defined in interface are implemented using
org.springframework.data.jpa.repository.support.SimpleJpaRepository of
Spring Data JPA.
@NoRepositoryBean // (1)
public interface MyProjectRepository<T, ID extends Serializable> extends
Repository<T, ID> { // (2)
// ...
Select and define the required methods from the methods of Repository interface of Spring Data.
(3)
Inherit this common interface and specify the type of entity in generic type <T> and type of
entity ID in generic type <ID extends Serializable>. In this example, Order type is
(4)
specified in entity and Integer type in entity ID.
This section explains how to create entity specific Repository interface without inheriting any interface of Spring
Data or common interface.
Repository can be created in this way when common entity operations are not required. The methods
having same signature as methods defined in Repository interface of Spring Data are implemented using
org.springframework.data.jpa.repository.support.SimpleJpaRepository pro-
vided by Spring Data JPA.
// ...
}
(3)
It is difficult to develop the actual application using only the Spring Data interface which is used for performing
generic CRUD operations.
Therefore Spring Data provides a mechanism to add “query methods” for performing any persistence operations
(SELECT/UPDATE/DELETE) for the entity specific Repository interface.
In the added query method, entity operations are performed using query language (JPQL or Native SQL).
JPQL is an abbreviation of “Java Persistence Query Language” and is the query language to perform en-
tity operations (SELECT/UPDATE/DELETE) corresponding to the records of persistence layer (DB). The
syntax is similar to SQL; however, JPQL operates the entities mapped to the records of persistence layer
instead of operating these records directly. The entity operations are reflected to persistence layer (DB)
using JPA provider (Hibernate).
For details on JPQL, refer to JSR 338: Java Persistence API, Version 2.1 Specification (PDF) “Chapter 4
Query Language”.
1.
@Query annotation In the method to be added to entity specific Repository
(Spring Data functionality) interface, specify
@org.springframework.data.jpa.repository.Query
annotation and the query to be executed.
When there is no specific reason, it is recommended that
you specify the query using this method.
2.
Method name based on naming Specify the query to be executed by assigning a method name
conventions as per Spring Data naming conventions.
(Spring Data functionality) Query (JPQL) is generated from the method name using Spring
Data JPA functionality. Only a SELECT clause of JPQL can be
generated.
For a simple query having few conditions, this method can
be used instead of using @Query annotation. However, for a
complex query with many conditions, a simple method name
indicating behavior should be used and Query should be
specified using @Query annotation.
3.
Named query of properties file Specify the query in a properties file.
(Spring Data functionality) The location of method definition (entity specific Repository
interface) and location wherein the query is specified
(properties file) are separated; hence this way of specifying
the query is not recommended.
However, when using Native SQL as query, check whether
it is necessary to define the database dependent SQL in the
properties file.
In case of applications for which any database can be selected
or when the database changes (or is likely to be changed)
depending on execution environment, then it is necessary to
specify the Query using this method and manage the Properties
file as environment dependent material.
Particularly, there is no restriction on using multiple query specification methods. Query specification
methods and restriction on their concurrent usage should be determined in accordance with the project.
The operations would be as follows since the Spring Data default setting is CREATE_IF_NOT_FOUND.
4. An error occurs when query (JPQL) cannot be created from method name.
For details on Query Lookup methods, refer to Spring Data Commons - Reference Documentation “Defin-
ing query methods” - “Query lookup strategies”.
@Query(value = "SELECT o FROM Order o WHERE o.status.code = :statusCode ORDER BY o.id DESC")
@Lock(LockModeType.PESSIMISTIC_WRITE) // (1)
List<Order> findByStatusCode(@Param("statusCode") String statusCode);
-- (2) statusCode='accepted'
SELECT
order0_.id AS id1_5_
,order0_.status_code AS status2_5_
FROM
t_order order0_
WHERE
order0_.status_code = 'accepted'
ORDER BY
order0_.id DESC
FOR UPDATE
It is recommended to perform update and delete operations on entity objects managed in EntityManager.
However, when entities need to be updated or deleted in a batch, check whether the entities of persistence layer
(DB) are operated using query method.
Operating the entities of persistence layer directly reduces the frequency of SQLs that would be required to
be executed for operating these entities. Therefore, in case of applications that demand high performance,
the causes of performance degradation can be reduced by operating the entities in batch using this method.
Such SQLs are as follows:
• SQL for loading all entity objects in EntityManager. Need not be executed.
• SQL for updating and deleting entity. This SQL was earlier required to be executed n times, but now
it is sufficient to execute it only once.
Note: Standards for deciding whether to operate entities of persistence layer directly
When operating the entities of persistence layer directly, since there are certain points to be careful about
from functionality point of view, in case of applications which do not demand high performance, it is
recommended that the batch operations must also be performed through the entity objects managed
in EntityManager. For the points to be careful, refer to the example below.
The example of directly operating the entities of persistence layer using query method is shown below.
@Modifying // (1)
@Query("UPDATE OrderItem oi SET oi.logicalDelete = true WHERE oi.id.orderId = :orderId ") //
int updateToLogicalDelete(@Param("orderId") Integer orderId); // (3)
(3) If update count or delete count is required, specify int or java.lang.Integer as return
value and if count is not required, specify void.
Setting QueryHints
@Query(value = "SELECT o FROM Order o WHERE o.status.code = :statusCode ORDER BY o.id DESC")
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(value = { @QueryHint(name = "javax.persistence.lock.timeout", value = "0") }) //
List<Order> findByStatusCode(@Param("statusCode") String statusCode);
(1) Specify hint name in name attribute of @QueryHint annotation and hint value in value
attribute.
In addition to the hint stipulated in JPA specifications, provider specific hint can be specified.
In the above example, lock timeout is set to 0 (DB used is PostgreSQL). “FOR UPDATE
NOWAIT” clause is added to SQL.
QueryHints stipulated in JPA specifications are as follows: For details, refer to JSR 338: Java Persistence
API, Version 2.1 Specification (PDF).
• javax.persistence.query.timeout
• javax.persistence.lock.timeout
• javax.persistence.cache.retrieveMode
• javax.persistence.cache.storeMode
For Hibernate specific QueryHints, refer to “3.4.1.8. Query hints” of Hibernate EntityManager User guide.
The method of specifying a query to be executed while calling query method is given below.
@Query(value = "SELECT o FROM Order o WHERE o.status.code = :statusCode ORDER BY o.id DESC")
List<Order> findByStatusCode(@Param("statusCode") String statusCode);
-- (2) statusCode='accepted'
SELECT
order0_.id AS id1_5_
,order0_.status_code AS status2_5_
FROM
t_order order0_
WHERE
order0_.status_code = 'accepted'
ORDER BY
order0_.id DESC
Native SQL converted from JPQL. Query(JPQL) specified in value attribute of @Query anno-
tation is converted to Native SQL of database to be used.
(2)
Native SQL can be specified as query instead of JPQL by setting nativeQuery attribute to true. Fun-
damentally it is recommended that you use JPQL; however, when there is a need to generate the
query that cannot be expressed in JPQL, Native SQL can be specified directly. To specify the database
dependent SQL, analyze whether it can be defined in the properties file.
For method of defining SQL in properties file, refer to “Specifying as Named query in Properties file”.
Named parameter can be used by assigning a name to bind parameter of the query
and using this assigned name to specify the value. To use Named Parameter, add
@org.springframework.data.repository.query.Param annotation to the argument
from which the value has to be used to bind to the named parameter in the query. Specify the assigned
parameter name in value attribute of param annotation. ON the query side, at the position where parameter
is to be bound in the query, specify it in the ”:parametername” format.
When there is no specific reason, It is recommended to use Named Parameters considering maintain-
ability and readability of query.
In case of LIKE search, if the type of matching (Forward match, Backward match and Partial match) is fixed,
"%" can be specified in JPQL.
However, this is in extended Spring Data JPA format and not in standard JPQL, so it can be specified only in
JPQL specified with @Query annotation.
An error occurs if "%" is specified in JPQL specified as Named query.
Backward match
2.
SELECT a FROM Account WHERE
%:parameterName
a.firstName LIKE %:firstName
or
SELECT a FROM Account WHERE
%?n a.firstName LIKE %?1
Partial match
3.
SELECT a FROM Account WHERE
%:parameterName%
a.firstName LIKE %:firstName%
or
SELECT a FROM Account WHERE
%?n% a.firstName LIKE %?1%
When it is necessary to change the type of matching (Forward match, Backward match and Partial match)
dynamically, "%" should be added before and after the parameter value (same as conventional method),
instead of specifying % in JPQL.
The method for converting into search condition value corresponding to the type of matching is provided
in org.terasoluna.gfw.common.query.QueryEscapeUtils class; this class can be used if
it meets the requirements. For details on QueryEscapeUtils class, refer to “Escaping during LIKE
search” of “Database Access (Common)”.
// (1)
@Query(value = "SELECT o FROM Order o WHERE o.status.code = :statusCode ORDER BY o.id DESC")
Page<Order> findByStatusCode(@Param("statusCode") String statusCode, Pageable pageable);
In addition to directly specifying the sort conditions in query, they can be specified in
org.springframework.data.domain.Sort object stored in Pageable object.
When specifying the sort conditions using this method, there is no need to specify countQuery attribute.
Example of sorting using Sort object stored in Pageable object is shown below.
• Controller
@RequestMapping("list")
public String list(@PageableDefault(
size=5,
sort = "id", // (1)
direction = Direction.DESC // (1)
) Pageable pageable,
Model model) {
Page<Order> orderPage = orderService.getOrders(pageable); // (2)
model.addAttribute("orderPage", orderPage);
return "order/list";
}
(1) Specify the sort conditions. Sort conditions are set in Sort object that can be fetched by
Pageable#getSort() method.
In the above example, DESC is specified as a sort condition for id field.
(2)
• Service (Caller)
(3)
• Repository interface
-- (5) statusCode='accepted'
SELECT
COUNT(order0_.id) AS col_0_0_
FROM
t_order order0_
WHERE
order0_.status_code = 'accepted'
-- (6) statusCode='accepted'
SELECT
order0_.id AS id1_5_
,order0_.status_code AS status2_5_
FROM
t_order order0_
WHERE
order0_.status_code = 'accepted'
ORDER BY
order0_.id DESC
LIMIT 5
(4)
(5)
(6) Native SQL for fetching the entity of the specified page location converted from JPQL.
Not specified in query; however “ORDER BY” clause is added to the condition specified in
Sort object stored in Pageable object. In this example, it is SQL for PostgreSQL.
Specify the query (JPQL) to be executed through method name as per the naming conventions of Spring Data
JPA.
JPQL is created from the method name by the functionality of Spring Data JPA.
However, this is only possible for SELECT queries and not for UPDATE and DELETE.
For naming conventions for creating JPQL, refer to the following pages.
Spring Data Commons - Reference Documenta- This section describes naming conventions
5.
tion “Appendix C. Repository query keywords” (keywords) for creating JPQL.
• OrderRepositry.java
(1) When the method name matches with ^(find|read|get).*By(.+) pattern, JPQL is
created from method name.
In the (.+) portion, specify the property of entity which forms the query condition or
keywords indicating the operation.
In the example, Order object where code property ( String type) value of status
property ( OrderStatus type) stored in Order object is matched with the specified
parameter value ( statusCode ), is being fetched in page format.
• Count Query
-- (2) JPQL
SELECT
COUNT(*)
FROM
ORDER AS generatedAlias0
LEFT JOIN generatedAlias0.status AS generatedAlias1
WHERE
generatedAlias1.code = ?1
COUNT(*) AS col_0_0_
FROM
t_order order0_
LEFT OUTER JOIN c_order_status orderstatu1_
ON order0_.status_code = orderstatu1_.code
WHERE
orderstatu1_.code = 'accepted'
(2)
(3)
-- (4) JPQL
SELECT
generatedAlias0
FROM
ORDER AS generatedAlias0
LEFT JOIN generatedAlias0.status AS generatedAlias1
WHERE
generatedAlias1.code = ?1
ORDER BY
generatedAlias0.id DESC;
-- (5) statusCode='accepted'
SELECT
order0_.id AS id1_5_
,order0_.status_code AS status2_5_
FROM
t_order order0_
LEFT OUTER JOIN c_order_status orderstatu1_
ON order0_.status_code = orderstatu1_.code
WHERE
orderstatu1_.code = 'accepted'
ORDER BY
order0_.id DESC
LIMIT 5
(4)
Native SQL for fetching the entities converted from JPQL of step (4).
(5)
Specify the query in the properties file (classpath:META-INF/jpa-named-queries.properties) of Spring Data JPA.
Consider using this method when it is required to write a database platform specific SQL at the time of
using NativeQuery.
Even if it is database platform specific SQL, it is recommended that you use a method to directly specify in
@Query annotation when there is no dependency on the execution environment.
• OrderRepositry.java
@Query(nativeQuery = true)
List<Order> findAllByStatusCode(@Param("statusCode") String statusCode); // (1)
As per default behavior, Lookup name is contructed by connecting class name of the entity linked with
method name using "." (dot). However, any name can be specified.
• For count at the time of page search, specify it in countName attribute of @Query annotation.
• jpa-named-queries.properties
# (3)
Order.findAllByStatusCode=SELECT * FROM order WHERE status_code = :statusCode
(3) Specify the SQL to be executed by using lookup name for the query as the key.
In the above example, the SQL to be executed has been specified by using
"Order.findAllByStatusCode" as key.
Tip: The method of specifying Named Query in any properties file instead of the properties file of Spring
Data JPA is explained below.
• xxx-infra.xml
Call a query method to fetch all entities that match the conditions.
• Repository interface
// (1)
@Query("SELECT a FROM Account a WHERE :createdDateFrom <= a.createdDate AND a.createdDate
List<Account> findByCreatedDate(
@Param("createdDateFrom") Date createdDateFrom,
@Param("createdDateTo") Date createdDateTo);
• Service
// (2)
List<Account> accounts = accountRepository.findByCreatedDate(fromDate,
toDate);
if (accounts.isEmpty()) { // (3)
// ...
}
return accounts;
}
(3) If the search result is 0 records, a blank list is returned. As null is not returned, null check is not
required.
If needed, implement the process when the search result is 0 records.
Amongst the entities matching the conditions, call a query method to fetch the entities of the specified page.
• Repository interface
// (1)
@Query("SELECT a FROM Account a WHERE :createdDateFrom <= a.createdDate AND a.createdDate
Page<Account> findByCreatedDate(
@Param("createdDateFrom") Date createdDateFrom,
@Param("createdDateTo") Date createdDateTo, Pageable pageable);
• Controller
@RequestMapping("list")
public String list(@RequestParam("targetDate") Date targetDate,
@PageableDefault(
page = 0,
value = 5,
sort = { "createdDate" },
direction = Direction.DESC)
Pageable pageable, // (2)
Model model) {
Page<Order> accountPage = accountService.getAccounts(targetDate, pageable);
model.addAttribute("accountPage", accountPage);
return "account/list";
}
• Service
// (3)
Page<Account> page = accountRepository.findByCreatedDate(fromDate,
toDate, pageable);
if (!page.hasContent()) { // (4)
// ...
}
return page;
}
(4) If the search result is 0 records, a blank list will be set in Page object and false will be
returned for Page#hasContent() method.
If needed, implement the process when the search result is 0 records.
To add query method to Repository for searching the entities as per dynamic conditions, search process should be
implemented by creating custom Repository interface and custom Repository class for the entity specific Reposi-
tory interface. For method of creating custom Repository interface and custom Repository class, refer to “Adding
individual custom method to entity specific Repository interface”.
Todo
TBD
Implement and call the query method for fetching all entities matching the dynamic conditions.
• Order ID
• Product name
Further, the search is narrowed down using AND operator for the orders matching the specified conditions. If no
condition is specified, a blank list will be returned.
• Criteria (JavaBean)
// ...
(2) Define a method in custom Repository interface which receives Criteria object as an argument
and returns List of Order objects.
@PersistenceContext
EntityManager entityManager; // (4)
// (7)
if (criteria.getId() != null) {
andConditions.add("o.id = :id");
bindParameters.put("id", criteria.getId());
}
if (!CollectionUtils.isEmpty(criteria.getStatusCodes())) {
andConditions.add("o.status.code IN :statusCodes");
bindParameters.put("statusCodes", criteria.getStatusCodes());
}
if (StringUtils.hasLength(criteria.getItemName())) {
joinConditions.add("o.orderItems oi");
joinConditions.add("oi.item i");
andConditions.add("i.name LIKE :itemName ESCAPE '~'");
bindParameters.put("itemName", QueryEscapeUtils
.toLikeCondition(criteria.getItemName()));
}
// (8)
if (andConditions.isEmpty()) {
return Collections.emptyList();
}
// (9)
// Create dynamic query.
final StringBuilder queryString = new StringBuilder();
// (10)
queryString.append("SELECT o FROM Order o");
// (11)
// add join conditions.
for (String joinCondition : joinConditions) {
queryString.append(" LEFT JOIN ").append(joinCondition);
}
// add conditions.
Iterator<String> andConditionsIt = andConditions.iterator();
if (andConditionsIt.hasNext()) {
queryString.append(" WHERE ").append(andConditionsIt.next());
}
while (andConditionsIt.hasNext()) {
queryString.append(" AND ").append(andConditionsIt.next());
}
// (12)
// add order by condition.
queryString.append(" ORDER BY o.id");
// (13)
// Create typed query.
final TypedQuery<Order> findQuery = entityManager.createQuery(
queryString.toString(), Order.class);
// Bind parameters.
for (Map.Entry<String, Object> bindParameter : bindParameters
.entrySet()) {
findQuery.setParameter(bindParameter.getKey(), bindParameter
.getValue());
}
// (14)
// Execute query.
return findQuery.getResultList();
(5) Implement the query method to fetch all the entities matching the dynamic conditions.
In the above example, the method is not split for explanation purpose; however it can be split if
required.
(6) Define the variables (list for AND condition, list for join condition, bind parameter map) to
build dynamic queries.
Set the required information in the variables for the items for which conditions are specified in
OrderCriteria object.
(7) Determine whether conditions are specified in OrderCriteria object and set the required
information for building dynamic queries.
In the above example, the information is set to fetch the items wherein id is completely
matched with the specified value, statusCodes is included in the specified list, and
itemName satisfying forward match with the specified value.
For itemName, the related-entities having the values to be compared have a complex nested
relation; hence these entities should be joined.
(8) In the above example, when conditions are not specified, return a blank list as there is no need
to perform search.
(9) When conditions are specified, build the query for searching entities.
In the above example, query to be executed is built using java.lang.StringBuilder
class.
(15) Inherit the custom Repository interface in entity specific Repository interface.
• Service (Caller)
// implementation of sample.
// (16)
OrderCriteria criteria = new OrderCriteria();
criteria.setId(conditionValueOfId);
criteria.setStatusCodes(conditionValueOfStatusCodes);
criteria.setItemName(conditionValueOfItemName);
List<Order> orders = orderRepository.findAllByCriteria(criteria); // (17)
if (orders.isEmpty()) { // (18)
// ...
}
(17) Use OrderCriteria as an argument and call the query method to get all the entities matching the
dynamic conditions.
(18) Analyze the search results and perform the processing required in case of 0 records.
• Executed JPQL(SQL)
-- (19)
-- conditionValueOfId=4
-- conditionValueOfStatusCodes = ["accepted"]
-- conditionValueOfItemName = "Wat"
-- JPQL
SELECT
o
FROM
ORDER o
JOIN o.orderItems oi
JOIN oi.item i
WHERE
o.id = :id
AND o.status.code IN :statusCodes
AND i.name LIKE :itemName ESCAPE '~'
ORDER BY
o.id
-- SQL
SELECT
order0_.id AS id1_6_
,order0_.created_by AS created2_6_
,order0_.created_date AS created3_6_
,order0_.last_modified_by AS last4_6_
,order0_.last_modified_date AS last5_6_
,order0_.status_code AS status6_6_
FROM
t_order order0_ INNER JOIN t_order_item orderitems1_
ON order0_.id = orderitems1_.order_id INNER JOIN m_item item2_
ON orderitems1_.item_code = item2_.code
WHERE
order0_.id = 4
AND (
order0_.status_code IN ('accepted')
)
AND (
item2_.name LIKE 'Wat%' ESCAPE '~'
)
ORDER BY
order0_.id
(19) Example of generated JPQL and SQL when all the conditions are specified.
-- (20)
-- conditionValueOfId=4
-- conditionValueOfStatusCodes = ["accepted"]
-- conditionValueOfItemName = ""
-- JPQL
SELECT
o
FROM
ORDER o
WHERE
o.id = :id
AND o.status.code IN :statusCodes
ORDER BY
o.id
-- SQL
SELECT
order0_.id AS id1_6_
,order0_.created_by AS created2_6_
,order0_.created_date AS created3_6_
,order0_.last_modified_by AS last4_6_
,order0_.last_modified_date AS last5_6_
,order0_.status_code AS status6_6_
FROM
t_order order0_
WHERE
order0_.id = 4
AND (
order0_.status_code IN ('accepted')
)
ORDER BY
order0_.id;
(20) Example of generated JPQL and SQL when conditions other than iteName are specified.
-- (21)
-- conditionValueOfId=4
-- conditionValueOfStatusCodes = []
-- conditionValueOfItemName = ""
-- JPQL
SELECT
o
FROM
ORDER o
WHERE
o.id = :id
ORDER BY
o.id
-- SQL
SELECT
order0_.id AS id1_6_
,order0_.created_by AS created2_6_
,order0_.created_date AS created3_6_
,order0_.last_modified_by AS last4_6_
,order0_.last_modified_date AS last5_6_
,order0_.status_code AS status6_6_
FROM
t_order order0_
WHERE
order0_.id = 4
ORDER BY
order0_.id;
Implement and call the query method to fetch the entities corresponding to the specified page, amongst the entities
matching the dynamic conditions.
As shown in the example below, the specification is same as that for normal search except for fetching the corre-
sponding page. Further, the description for fetching all records is omitted.
(1) Define the query method to fetch the entities corresponding to the specified page, amongst the
entities matching the dynamic conditions.
@PersistenceContext
EntityManager entityManager;
if (criteria.getId() != null) {
andConditions.add("o.id = :id");
bindParameters.put("id", criteria.getId());
}
if (!CollectionUtils.isEmpty(criteria.getStatusCodes())) {
andConditions.add("o.status.code IN :statusCodes");
bindParameters.put("statusCodes", criteria.getStatusCodes());
}
if (StringUtils.hasLength(criteria.getItemName())) {
joinConditions.add("o.orderItems oi");
joinConditions.add("oi.item i");
andConditions.add("i.name LIKE :itemName ESCAPE '~'");
bindParameters.put("itemName", QueryEscapeUtils.toLikeCondition(criteria
.getItemName()));
}
if (andConditions.isEmpty()) {
List<Order> orders = Collections.emptyList();
return new PageImpl<Order>(orders, pageable, 0); // (3)
}
// add conditions.
Iterator<String> andConditionsIt = andConditions.iterator();
if (andConditionsIt.hasNext()) {
conditionsString.append(" WHERE ").append(andConditionsIt.next());
}
while (andConditionsIt.hasNext()) {
conditionsString.append(" AND ").append(andConditionsIt.next());
}
queryString.append(conditionsString); // (6)
countQueryString.append(conditionsString); // (6)
// bind parameters.
for (Map.Entry<String, Object> bindParameter : bindParameters
.entrySet()) {
countQuery.setParameter(bindParameter.getKey(), bindParameter
.getValue()); // (8)
findQuery.setParameter(bindParameter.getKey(), bindParameter
.getValue());
}
(2) Implement the query method to fetch the entities corresponding to the specified page, amongst
the entities matching the dynamic conditions.
In the above example, the method is not split for explanation purpose; however it can be split if
required.
(3) In the above example, when conditions are not specified, return a blank page information as
there is no need to perform search.
(4) Create variables to build the queries to fetch records and to build the conditions (join condition
and AND condition).
Same conditions need to be used in query for fetching entities and in query for fetching records;
hence variables are created to build the conditions (join condition and AND condition).
(6) Build the dynamic query elements for query to fetch entities and query to fetch records.
(7) Sort conditions (ORDER BY clause) are built as dynamic query elements for the query to fetch
entities.
Utility component
(org.springframework.data.jpa.repository.query.QueryUtils) provided
by Spring Data JPA is used for building the ORDER BY clause.
(8) Convert the query string for fetching the dynamically built records into
javax.persistence.TypedQuery and set the bind parameter for required for executing
the query.
(9) Execute the query for fetching the records and get the total number of records matching the
conditions.
1376 6 Data Access
(10) If the entities matching the conditions exist, execute the query for fetching the entities and fetch
the information of the corresponding page.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• Service (Caller)
// implementation of sample.
OrderCriteria criteria = new OrderCriteria();
criteria.setId(conditionValueOfId);
criteria.setStatusCodes(conditionValueOfStatusCodes);
criteria.setItemName(conditionValueOfItemName);
Page<Order> orderPage = orderRepository.findPageByCriteria(criteria,
pageable); // (13)
if (!orderPage.hasContent()) {
// ...
}
(13) Call the query method to fetch the entities corresponding to the specified page, amongst the
entities matching the dynamic conditions.
If the ID (Primary Key) is known, fetch the entity object by calling the findOne method of Repository interface.
(1) Specify the ID (Primary Key) of entity and call the findOne(ID) method of Repository interface.
(2) When the specified entity ID does not exist, the return value would be null, hence null check is
necessary.
If needed, implement the process which is carried out when the specified entity ID does not
exist.
When the entity object of specified ID is already managed by EntityManager, entity object managed by
EntityManager is returned without accessing the persistence layer (DB). Therefore, if findOne method
is used, unnecessary access to persistence layer can be controlled.
Load of the related-entity during query execution is determined based on the value
specified in fetch attribute of annotations (@javax.persistence.OneToOne
, @javax.persistence.OneToMany, @javax.persistence.ManyToOne ,
@javax.persistence.ManyToMany ).
Default values of fetch attribute differ depending on annotations. See the default values below:
(1) When value attribute is not specified, the entities are sorted in ascending order of ID.
For details, refer to JSR 338: Java Persistence API, Specification (PDF) of Version 2.1
“11.1.42 OrderBy Annotation”.
Todo
TBD
• Example of cases wherein it is better to change fetch attribute from default value.
When the ID is not known, call the query method to search entities by conditions other than ID.
• Repository interface
// (1)
@Query("SELECT a FROM Account a WHERE a.accountId = :accountId")
Account findByAccountId(@Param("accountId") String accountId);
(1) Define the query method to search 1 record of entity by specifying items other than ID as
conditions.
• Service
(3) Similar to findOne method of Repository interface, when the entities matching the conditions
do not exist, null will be returned. Hence null check is necessary.
If needed, implement the process for the scenario when entities matching the specified
conditions do not exist.
When multiple entities matching the conditions exist,
org.springframework.dao.IncorrectResultSizeDataAccessException
occurs.
When a query method is called, the query is always executed on persistence layer (DB). However, when
the entities which are fetched by executing the query are already being managed in EntityManager,
the entity objects fetched from DB are discarded and the entity objects managed in EntityManager are
returned.
It is recommended not to create a query method wherein ID + α is used as condition. It can be implemented
by creating a logic that compares property value of entity objects fetched by calling findOne method.
The reason for not recommending the creation of this query method is that there is a possibility of the entity
objects fetched by executing the query getting discarded and unnecessary query getting executed. If the
ID is known, it is desirable to use findOne method which prevents execution of unnecessary queries. This
should be consciously implemented especially in case of applications with high performance requirements.
However, if the following conditions are applicable, use of query method may reduce the frequency of
query execution; hence query method can be used in such cases.
• Properties of related objects (column of related table) are included in a part of + α condition.
Related-entities specified in JOIN FETCH are loaded immediately after executing the query.
The related-entities not specified in JOIN FETCH performs the following operations as per the values
specified in fetch attribute of associated annotations ( @OneToOne , @OneToMany , @ManyToOne ,
@ManyToMany ).
• The sort order of the related-entities specified in JOIN FETCH is controlled by specifying “ORDER
BY” clause in JPQL.
• The sort order of the related-entities loaded after executing the query is controlled by specifying
@javax.persistence.OrderBy annotation to the property of the related-entities.
Adding entities
In order to add an entity, create an entity object and call the save method of Repository interface.
• Service
(1) Create an instance of entity object and set the values for required properties.
In the above example, ID generator of JPA is used for setting the ID. When ID generator of JPA
is to be used, ID should not be set in the application code.
If you set the ID in the application code, merge method of EntityManager gets called
leading to execution of unnecessary processing.
(2) Call the save method of Repository interface and manage the entity objects created in (1) in
EntityManager.
Take note that entity object passed as an argument of save method will not be the one managed
by EntityManager, but the entity object returned by save method will be the one managed
by EntityManager.
ID is set by the ID generator of JPA at the time of this process.
merge method of EntityManager has a mechanism to fetch the entities having same ID from persis-
tence layer (DB), when the entities are to be managed in EntityManager. Process to fetch the entities
becomes unnecessary while adding the entities. In case of application with high performance requirements,
ID generation timing should also be taken into account.
When save method is called, query (INSERT) is not executed in the persistence layer (DB). Therefore,
when constraint error such as unique constraint violation needs to be handled, saveAndFlush method or
flush method should be called instead of save method of Repository interface.
• Entity
@Entity // (3)
@Table(name = "t_order") // (4)
public class Order implements Serializable {
// (5)
// (7)
@ManyToOne
@JoinColumn(name = "status_code")
private OrderStatus status;
// ...
// ...
(4) Specify the table name to be mapped with the entity class in name attribute of
@javax.persistence.Table annotation.
The table name need not be specified if it can be resolved from entity name; however, it has to
be specified if it cannot be resolved from entity name.
(5) The annotations required for using ID generator of JPA are being specified.
When using ID generator of JPA, specify @javax.persistence.GeneratedValue
annotation.
When using sequence object, @javax.persistence.SequenceGenerator annotation
should be specified. When using table generator,
@javax.persistence.TableGenerator annotation should be specified.
In the above example, ID is generated using the sequence object called "s_order_id" name.
(6) Assign @javax.persistence.Id annotation to the property that holds the primary key.
In case of composite key, assign @javax.persistence.EmbeddedId annotation.
For details on each annotation, refer to JSR 338: Java Persistence API, Version 2.1 Specification (PDF).
• AUTO: Generate ID by selecting the most appropriate method in persistence layer (DB).
Generally it is recommended to explicitly specify the type to be used instead of using AUTO.
In order to add parent-entity and related-entity together, call the save method of Repository interface and manage
the entity objects under EntityManager. Then create the related-entity objects and map them with parent-
entity objects.
In order to use this method, persist needs to be included in the cascade operation of the related-entity.
When the related-entity is specified under cascade operations, JPA operations for parent-entity (persist,
merge, remove, refresh, detach) are linked with related-entity and then carried out.
Mapping with the operations of Repository interface of Spring Data JPA is as follows:
refresh, detach are not executed in default implementation of Spring Data JPA.
• Service
(1) First, call the save method of Repository interface and bring the entity object under
EntityManager.
In the above example, save method is being called before setting the related-entity objects. This
is because it is necessary to generate the ID (orderId) of parent-entity; this ID is used as a part
of ID of the related entity.
(2) Set the related-entity objects for the parent-entity object (that are to be managed under
EntityManager).
When committing the transaction, persist operation (INSERT) on parent-entity object is linked
with the related-entity objects.
• Entity
@Entity
@Table(name = "t_order")
public class Order implements Serializable {
@Id
@SequenceGenerator(name = "GEN_ORDER_ID", sequenceName = "s_order_id",
allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "GEN_ORDER_ID")
private int id;
@ManyToOne
@JoinColumn(name = "status_code")
private OrderStatus status;
// ...
// ...
• Related-entity
@Entity
@Table(name = "t_order_item")
public class OrderItem implements Serializable {
@EmbeddedId
private OrderItemPK id;
@ManyToOne
@JoinColumn(name = "order_id", insertable = false, updatable = false)
private Order order;
// ...
// ...
@Entity
@Table(name = "t_order_pay")
public class OrderPay implements Serializable {
@Id
@Column(name = "order_id")
private Integer orderId;
@Column(name = "way_to_pay")
private String wayToPay;
@OneToOne
@JoinColumn(name = "order_id")
private Order order;
// ...
// ...
In order to add a related-entity, link the newly created related-entity object with the parent-entity object fetched
through Repository interface.
For using this method, persist and merge should be included in the cascade operation of the related-entity.
(2) In case of entity with 1: N relationship, add the related-entity object to the collection fetched
from the parent-entity object.
(3) In case of entity with 1:1 relationship, set the related-entity object in the parent-entity object.
When a related-entity object is to be added directly without linking it with the parent-entity object, save it using
the Repository interface of related-entity.
orderItemRepository.save(orderItem); // (2)
The number of objects created is less. When fetching the parent-entity object, related-entity objects which
are not necessary for the processing may also get created.
When ID of the parent-entity is being used as a part of the related-entity ID, the ID should be set before
calling the save method. If ID is set, merge method of EntityManager is called as per the default
implementation of Spring Data JPA. Therefore, the entity with same ID is always fetched from persistence
layer (DB).
Warning: Points to be noted at the time of using parent-entity object after the related-entity is
added
When the related-entity is added using Repository save method of related-entity, it cannot be fetched
through the parent-entity object since it is not linked with the parent-entity object.
To avoid this problem,
1. Related-entity object should not be added directly. It should first be linked with the parent-entity
object, and then added.
2. Synchronization with persistence layer (DB) should be done before fetching the parent-entity,
using saveAndFlush method.
In such a case, if there is no specific reason, objects of the related-entity should be added by the former
method. In the latter method, the problem cannot be avoided if the parent-entity object is already the
“managed” entity.
Updating entities
In order to update the entity, set the changed value to the entity object fetched using the Repository interface
method.
The entity object fetched using Repository interface method are managed under EntityManager. For
the entity objects managed under EntityManager, just by changing the state of objects using setter
method, the changes are reflected in persistence layer (DB) at the time of committing the transaction.
Therefore, there is no need to explicitly call the save method of Repository interface.
However, save method needs to be called when the entity objects are not managed under
EntityManager. For example, when entity object is created based on the request parameters sent from
the screen.
In order to update the related-entity, first fetch the related-entity from the parent entity which in turn can be fetched
using Repository interface and then set the values to be updated in related-entity object.
For using this method, merge should be included in the cascade operation of related-entity.
order.getOrderPay().setWayToPay("cash"); // (3)
(2) In case of entity with 1:N relationship, the state of related-entity object stored in the collection
fetched from parent-entity object is to be updated by calling the setter method.
(3) In case of entity with 1:1 relationship, the state of related-entity object fetched from
parent-entity object is to be updated by calling the setter method.
In order to update the related-entity directly without using the parent-entity, fetch the related-entity directly using
its corresponding Repository interface and set the value to be changed.
orderItem.setQuantity(quantity); // (2)
(1) Call findOne method of Repository interface of the related-entity and fetch the related-entity
object.
Note: Behavior at the time of using parent-entity after the related-entity is updated
When the related-entity is updated using save method of Repository for related-entity, the related-entity
stored in the parent-entity object is also updated unlike the case wherein the related-entity is added. This is
because the parent-entity stores the reference of same instance which is managed under EntityManager.
Use query method to update the entity of persistence layer (DB) directly.
For details, refer to “Operating the entities of Persistence Layer directly”.
Deleting entities
In order to delete parent-entity and related-entity together, call delete method of Repository interface.
For using this method, remove should be included in the cascade operation of the related-entity or the setting for
deleting the related-entity should be enabled (orphanRemoval attribute should be set to true).
• Service
orderRepository.delete(orderId); // (1)
(1) Specify ID or entity object and call delete method of Repository interface.
• Entity
@Entity
@Table(name = "t_order")
public class Order implements Serializable {
// ...
@OneToMany(mappedBy = "order",
cascade = CascadeType.ALL, orphanRemoval = true) // (2)
@OrderBy
private Set<OrderItem> orderItems;
// ...
(2) Include remove in cascade operations and enable the setting to delete the related-entity (set
orphanRemoval attribute of the associated annotation to true).
If you do not want to delete the related-entity object, perform settings such that remove is not included in
cascade operation. Also, set orphanRemoval attribute to false.
In order to delete the related-entity, delete the related-entity object from the entity objects fetched through Repos-
itory interface.
For using this method, setting to delete the related-entity should be enabled (orphanRemoval attribute of the
associated annotation should be set to true).
• In case of entity with 1:N relationship, if the related-entity object is deleted from the collection, it is
also deleted from the persistence layer (DB) at the time of committing the transaction.
• In case of entity with 1:1 relationship, if the related-entity is set to null, it is also deleted from the
persistence layer (DB) at the time of committing the transaction.
When the value of orphanRemoval attribute is set to false (which is also the default value), the related-
entity is cleared from the memory; however persistence operation (UPDATE/DELETE) is not carried out
for deleted related-entity object.
• Service
// (2)
Set<OrderItem> orderItemsOfRemoveTarget = new LinkedHashSet<OrderItem>(); // (3)
for (OrderItem orderItem : order.getOrderItems()) {
String itemCode = orderItem.getId().getItemCode();
if (quantityMap.containsKey(itemCode)) {
int newQuantity = quantityMap.get(itemCode);
orderItem.setQuantity(newQuantity);
} else {
orderItemsOfRemoveTarget.add(orderItem); // (4)
}
}
order.getOrderItems().removeAll(orderItemsOfRemoveTarget); // (5)
order.setOrderPay(null); // (6)
(2) Describe the example to delete the entity with 1:N relationship.
(5) Delete it from the collection fetched from the related-entity object to be deleted.
(6) In case of entity with 1:1 relationship, set the property to that stores the related-entity object to
be deleted to null.
• Entity
@Entity
@Table(name = "t_order")
public class Order implements Serializable {
@Id
@SequenceGenerator(name = "GEN_ORDER_ID", sequenceName = "s_order_id", allocationSize = 1
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "GEN_ORDER_ID")
private int id;
@ManyToOne
@JoinColumn(name = "status_code")
private OrderStatus status;
// ...
(7) Enable the setting to delete the related-entity (set orphanRemoval attribute to true).
There are 2 associated annotations that can be specified for orphanRemoval attribute; @OneToOne and
@OneToMany.
In order to delete the related-entity without using the parent-entity, call the delete method of Repository interface
of related-entity.
(1) Specify ID or entity object and call delete method of Repository interface of the related-entity.
Use query method in order to directly delete the entity from persistence layer (DB).
For details, refer to “Operating the entities of Persistence Layer directly”.
Escaping the values to be used as search criteria should be done for LIKE search.
Escaping for LIKE search can be done using
org.terasoluna.gfw.common.query.QueryEscapeUtils class method of the common library.
For specifications of escaping in common library, refer to “Escaping during LIKE search” of “Database Access
(Common)”.
For the escaping method provided by common library, see the description below.
When type of matching (Forward match, Backward Match, Partial Match) is to be specified as JPQL, use the
method that performs only escaping.
• Repository
// (1) (2)
@Query("SELECT a FROM Article a WHERE"
+ " (a.title LIKE %:word% ESCAPE '~' OR a.overview LIKE %:word% ESCAPE '~')")
Page<Article> findPageBy(@Param("word") String word, Pageable pageable);
(1) Specify wildcard characters ( "%" or "_" ) for LIKE search in JPQL to be specified in
@Query annotation.
In the above example, the type of matching is set to partial match by specifying wildcard ( "%"
) before and after the argument word.
(2) In case of escaping provided by the common library, "~" is being used as escape characters;
hence specify "ESCAPE ’~’" after LIKE clause.
As described in [Specifying the query using @Query annotation], the wildcard character "%" can be use
directly in JPQL only if the @Query annotation is used. The wildcard character "_" cannot use directly
in LIKE search of JPQL. It can be used in the following two ways.
2. Concatenate the wildcard character "_" with bind variable using database string concatenation func-
tion such as CONCAT .
• Service
@Transactional(readOnly = true)
public Page<Article> searchArticle(ArticleSearchCriteria criteria,
Pageable pageable) {
return page;
}
(4) Pass the value escaped for LIKE search as the argument of query method.
When the type of matching (Forward match, Backward match, Partial match) is to be determined in logic, use the
method that assigns wildcard to the escaped values.
• Repository
// (1)
@Query("SELECT a FROM Article a WHERE"
+ " (a.title LIKE :word ESCAPE '~' OR a.overview LIKE :word ESCAPE '~')")
Page<Article> findPageBy(@Param("word") String word, Pageable pageable);
(1) Do not specify wildcard for LIKE search in JPQL to be specified in @Query annotation.
• Service
@Transactional(readOnly = true)
public Page<Article> searchArticle(ArticleSearchCriteria criteria,
Pageable pageable) {
return page;
}
(2) When the type of matching is to be specified in logic, call any of the following methods and
perform escaping for LIKE search and assign wildcard for LIKE search.
QueryEscapeUtils#toStartingWithCondition(String)
QueryEscapeUtils#toEndingWithCondition(String)
QueryEscapeUtils#toContainingCondition(String)
(3) Pass the value escaped for LIKE search + assigned with wildcard as an argument of query
method.
JOIN FETCH
JOIN FETCH is a mechanism to reduce the number of queries generated for fetching entities, by joining and
fetching the related-entities in batch.
When fetching the entities by executing any query, make sure to perform JOIN FETCH for properties where
fetch attribute of associated annotation is EAGER.
If JOIN FETCH is not performed, it may impact performance since queries to fetch the related-entities will be
generated separately.
For properties where fetch attribute of related-annotation is LAZY, consider the frequency of use and determine
whether or not to perform JOIN FETCH.
JOIN FETCH should be performed in case of related-entities that are always referenced. However, if the cases
wherein related-entities will be referenced are only few, then it is better to fetch the entities by executing the
queries at the time of initial access instead of performing JOIN FETCH.
• Repository
In the above example, articleClass property of Article class is specified as the JOIN FETCH
target.
Note: JOIN FETCH is used as one of the solutions for N+1 problem. For N+1 problem, refer to “How to
resolve N+1”.
Spring Data provides mechanism to add any custom methods for the Repository interface.
See the description below for adding individual custom method to entity specific Repository interface.
// (1)
public interface OrderRepositoryCustom {
// (2)
Page<Order> findByCriteria(OrderCriteria criteria, Pageable pageable);
// (3)
public class OrderRepositoryImpl implements OrderRepositoryCustom {
@PersistenceContext
EntityManager entityManager; // (4)
// (5)
public Page<Order> findByCriteria(OrderCriteria criteria, Pageable pageable) {
// ...
return new PageImpl<Order>(orders, pageable, totalCount);
}
• Service(Caller)
(7) Methods of entity specific Repository interface can be called similar to other methods.
In the above example, if OrderRepository#findByCriteria(OrderCriteria,
Pageable) is called,
OrderRepositoryImpl#findByCriteria(OrderCriteria, Pageable) is
executed.
See the description below for adding the custom methods to all Repository interfaces in batch.
The method added in the example given below checks the version of the fetched entity and throws an optimistic
exclusion error in case of a mismatch.
// (1)
@NoRepositoryBean // (2)
public interface MyProjectRepository<T, ID extends Serializable> extends
JpaRepository<T, ID> {
// (3)
T findOneWithValidVersion(ID id, Integer version);
(1) Create a common Repository interface for defining the methods to be added.
In the above example, common Repository interface is being created by inheriting
JpaRepository of Spring Data.
(2) Annotation to prevent common Repository interface implementation class (in this example,
MyProjectRepositoryImpl) from being automatically registered as Repository Bean.
When common Repository interface implementation class is to be stored under the package
specified in base-package attribute of <jpa:repositories> element, there will be an error at
start-up unless this annotation is specified.
(3) Define the method to be added. In the example, method to check the version of fetched entity is
being added.
// (6)
public class MyProjectRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements MyProjectRepository<T, ID> {
// (7)
public MyProjectRepositoryImpl(
JpaEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
try {
return domainClass.getMethod("getVersion");
} catch (NoSuchMethodException | SecurityException e) {
return null;
}
}
// (9)
public T findOneWithValidVersion(ID id, Integer version) {
if (versionMethod == null) {
throw new UnsupportedOperationException(
String.format(
"Does not found version field in entity class. class is '%s'.",
entityInformation.getJavaType().getName()));
}
T entity = findOne(id);
return entity;
}
(6) Create an implementation class of common Repository interface (in this example,
MyProjectRepository).
In the above example, implementation class is being created by inheriting ‘‘
SimpleJpaRepository‘‘ of Spring Data.
• Factory class for creating instances of common Repository interface implementation class
// (10)
private static class MyProjectRepositoryFactory<T, ID extends Serializable>
extends JpaRepositoryFactory {
// (11)
public MyProjectRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
// (12)
protected JpaRepository<T, ID> getTargetRepository(
RepositoryMetadata metadata, EntityManager entityManager) {
@SuppressWarnings("unchecked")
JpaEntityInformation<T, ID> entityInformation = getEntityInformation((Class<T>) metad
.getDomainType());
return repositoryImpl;
}
// (14)
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return MyProjectRepository.class;
}
}
(10) Create a Factory class for creating instances of common Repository interface implementation
class.
In the example,
org.springframework.data.jpa.repository.support.JpaRepositoryFactory
is being inherited in order to restrict the implementation of Factory class.
(11) Define a constructor that takes EntityManager as argument and call constructor of parent
class.
(12) Override the method creating instances of implementation class. Create and return instances of
the created implementation class (in this example, MyProjectRepositoryImpl).
(13) This code is required to specify the lock mode using @Lock annotation for findOne method
and findAll methods of SimpleJpaRepository class of Spring Data JPA.
Make sure to implement this piece of logic, since it is implemented in the method of
JpaRepositoryFactory which is being overridden.
This process is required when the lock mode is to be specified using findOne and findAll
methods amongst the methods to be extended.
(14) Override the method that returns base interface of entity specific Repository interface, and
return the created interface (in this example, MyProjectRepository) type.
// (15)
public class MyProjectRepositoryFactoryBean<R extends JpaRepository<T, ID>, T, ID extends Ser
extends JpaRepositoryFactoryBean<R, T, ID> {
// (16)
protected RepositoryFactorySupport createRepositoryFactory(
EntityManager entityManager) {
return new MyProjectRepositoryFactory<T, ID>(entityManager);
}
}
(16) Override the method used for creating the Factory class instances. Then, create and return
instances of the created Factory class.
(17) Inherit all entity specific Repository interfaces from the common interface (In this example:
MyProjectRepository).
• xxx-infra.xml
<jpa:repositories base-package="x.y.z.domain.repository"
factory-class="x.y.z.domain.repository.MyProjectRepositoryFactoryBean" /> <!-- (18) -->
(18) Specify class name of the created FactoryBean class (in the example,
MyProjectRepositoryFactoryBean) in factory-class attribute of <jpa:repositories>
element.
• Service(Caller)
// ....
return order;
}
(19) The methods of entity specific Repository interface can be called similar to other methods.
In the above example, when
OrderRepository#findOneWithValidVersion(Integer, Integer) is called,
MyProjectRepositoryImpl#findOneWithValidVersion(Integer,
Integer) is executed.
Query fetch results can be mapped to other objects than entities. Use this method when records stored in persis-
tence layer (DB) are to be handled as objects (JavaBean) other than entity.
1. When the aggregated information is to be fetched using Aggregate function in query, it is not possible
to map the aggregation result to entity; hence it should be mapped with different objects.
2. In order to refer to only a part of information in a huge entity, or of a related-entity with complex
nesting, there may be cases wherein it is desirable to map the results with JavaBean containing only
the necessary properties. This is because processing performance may get hampered due to the fact
that mapping is carried out for items which are not needed in application processing or fetching of
unnecessary information during the processing leading to memory exhaustion. It may be obtained as
entity if there is no significant impact on processing performance.
• JavaBean
// (1)
public class OrderSummary implements Serializable {
// ...
// ...
(2) Create a constructor for generating objects using query execution results.
• Repository interface
// (3)
@Query("SELECT NEW x.y.z.domain.model.OrderSummary(o.id, SUM(i.price*oi.quantity))"
+ " FROM Order o LEFT JOIN o.orderItems oi LEFT JOIN oi.item i"
+ " GROUP BY o.id ORDER BY o.id DESC")
List<OrderSummary> findOrderSummaries();
This section introduces a mechanism to set values to Audit properties (Created By, Created Date-Time, Last
Modified By, Last Modified Date-Time) of persistence layer and method to apply the same.
Spring Data JPA provides a mechanism to set values to Audit properties for newly created entities as well as mod-
ified entities. This mechanism facilitates separation of logic of setting values to Audit properties from application
code such as Service etc.
Note: Reasons to separate the logic to set values to Audit properties from application code
1. Setting the values to Audit properties is generally not an application requirement, but is essential as
per the audit requirements of data. This logic does not essentially belong to application code such as
Service etc.; hence it is better to separate it from application code.
2. As per JPA specifications, only when the values of the entities fetched from persistence layer change,
the changes will be reflected in the persistence layer (by executing the SQL), thereby avoiding the
unnecessary access to persistence layer. If the values are set unconditionally in Audit properties in
the application code such as Service, even if only Audit properties change, the changes are reflected in
persistence layer making the effective JPA functionality ineffective. This problem can be avoided if the
values are set in Audit properties only when if they are changed; however it cannot be recommended
as it makes the application code complex.
Note: When Audit column of the persistence layer needs to be updated irrespective of the change in
values
If updating the Audit columns (Last Modified By, Last Modified Date-Time) even if there is no change in
values is a part of application specifications, then it becomes necessary to set Audit properties in application
code such as Service etc.
However, in such a case, the data modeling or application specifications are likely to be incorrect; hence it
is better to revise these specifications.
• Entity class
// ...
@Column(name = "created_by")
@CreatedBy // (1)
private String createdBy;
@Column(name = "created_date")
@CreatedDate // (2)
@Column(name = "last_modified_by")
@LastModifiedBy // (5)
private String lastModifiedBy;
@Column(name = "last_modified_date")
@LastModifiedDate // (6)
@Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime") // (3)
private DateTime lastModifiedDate; // (4)
// ...
(4) Field type that stores the “Created date-time” supports org.joda.time.DateTime,
java.util.Date, java.util.Calendar, java.lang.Long, long types as well
Date and Time APIs introduced in Java 8.
This is applicable for “Last modified date” field also.
Warning: @Type annotation is a Hibernate specific annotation and not a standard JPA annotation.
// (7)
@Component // (8)
public class SpringSecurityAuditorAware implements AuditorAware<String> {
// (9)
public String getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((UserDetails) authentication.getPrincipal()).getUsername();
}
(8) By assigning @Component annotation, this class comes under the target of component-scan.
Without assigning @Component annotation, it is also ok to define a bean of this class in bean
definition file.
(9) Return the object to be set in the properties of entity operator (Created by or Last Modified by).
In the above example, login user name (string) authenticated by SpringSecurity is returned as
entity operator.
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener
class="org.springframework.data.jpa.domain.support.AuditingEntityListener
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
(10) Specify
org.springframework.data.jpa.domain.support.AuditingEntityListener
class as Entity Listener.
Values are set in Audit properties of the methods implemented in this class.
• infra.xml
(11) Specify bean of class for resolving entity operator created in (7) in auditor-aware-ref attribute
of <jpa:auditing> element.
In the above example, component-scan is performed for SpringSecurityAuditorAware
implementation class; hence bean name "springSecurityAuditorAware" is specified.
Extended example of changing the method to create the values to be used is shown below.
// (1)
@Component // (2)
public class AuditDateTimeProvider implements DateTimeProvider {
@Inject
JodaTimeDateFactory dateFactory;
// (3)
@Override
public Calendar getNow() {
(3) Return the instances to be set in the properties of entity operation date-time (Created Date-Time
or Last Modified Date-Time).
In the above example, the instances fetched from
org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory of
common library are returned as Operation Date-Time.
For details on JodaTimeDateFactory, refer to “System Date”.
• infra.xml
<jpa:auditing
auditor-aware-ref="springSecurityAuditorAware"
date-time-provider-ref="auditDateTimeProvider" /> <!-- (4) -->
(4) In date-time-provider-ref attribute of <jpa:auditing> element, specify the bean of class to return
the value set in entity operation date-time created in (1).
In the above example, component-scan is performed for AuditDateTimeProvider
implementation class; hence bean name "auditDateTimeProvider" is specified.
This section introduces a mechanism to add common conditions to JPQL for fetching the entities from persistence
layer (DB). This is an extended Hibernate function and not included in standard JPA specifications.
As per data monitoring and data storage period requirements, when an entity is to be deleted, the application
should be designed such that it will delete the record logically (UPDATE of Logical Delete flag) and will
not delete (DELETE) the record physically. In case of such applications, the records deleted logically need
to be uniformly excluded from the search target; hence rather than writing the condition to exclude logically
deleted records in each and every query, it is better to specify it as a common condition.
The method to add common conditions for JPQL which is executed at the time of calling Repository interface
methods is as follows:
• Entity
@Entity
@Table(name = "t_order")
@Where(clause = "is_logical_delete = false") // (1)
public class Order implements Serializable {
// ...
@Id
private Integer id;
// ...
}
• SQL executed at the time of calling the findOne method of Repository interface
SELECT
-- ....
FROM
t_order order0_
WHERE
order0_.id = 1
AND (
order0_.is_logical_delete = false -- (2)
);
If SQL specific keywords are specified in @Where annotation, Hibernate may recognize SQL specific
keywords as a common string value and as a result expected SQL may not get generated. It is necessary to
extend the Dialect in case of SQL specific keywords are used in @Where annotation.
• Extending Dialect to register standard keywords such as true, false and unknown.
package com.example.infra.hibernate;
(1) By default Hiberanate-4.3 may not correctly process some of the SQL keywords. The
BOOLEAN type keywords such as true, false and unknown are not registered in
PostgreSQL dialect org.hibernate.dialect.PostgreSQL9Dialect. Therefore
such keywords are recognized as a common string value and as a result incorrect SQL may get
generated.
It is necessary to extend org.hibernate.dialect.Dialect dialect in order to register
such keywords.
(2) Register the SQL keywords that are likely to be used in @Where annotation.
<bean id="jpaVendorAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="com.example.infra.hibernate.ExtendedPostgreSQL9D
// ...
</bean>
@Where annotation is valid only in the class with @Entity. i.e. it is not assigned to SQL even if it is
assigned to the base entity class with @javax.persistence.MappedSuperclass.
Warning: @Where annotation is a Hibernate specific annotation and not the standard JPA annotation.
The method for adding common conditions for JPQL is shown below. JPQL is used for fetching the related-entities
of the parent-entity which is fetched by calling Repository interface methods.
• Entity
@Entity
@Table(name = "t_order")
@Where(clause = "is_logical_delete = false")
public class Order implements Serializable {
// ...
@Id
private Integer id;
SELECT
-- ...
FROM
t_order_item orderitems0_
WHERE
(orderitems0.is_logical_delete = false) -- (2)
AND orderitems0_.order_id = 1
ORDER BY
orderitems0_.item_code ASC
,orderitems0_.order_id ASC;
• SQL(Eager Load/Join Fetch) generated at the time of fetching parent-entity and its related-entity simulta-
neously
SELECT
-- ...
FROM
t_order order0_
LEFT OUTER JOIN t_order_item orderitems1_
ON order0_.id = orderitems1_.order_id
AND (orderitems1_.is_logical_delete = false) -- (2)
WHERE
order0_.id = 1
AND (
order0_.is_logical_delete = false
)
ORDER BY
orderitems1_.item_code ASC
,orderitems1_.order_id ASC;
Adding common conditions to SQL generated at the time fetching the related-entities is possible only for
the entities having @OneToMany and @ManyToMany relationship.
Warning: @Where annotation is a Hibernate specific annotation and not the standard JPA annotation.
Todo
TBD
Todo
TBD
6.4.1 Overview
Exclusive control is a process to maintain data consistency when same data is to be updated simultaneously by
multiple transactions.
Exclusive control should be performed when same data is likely to get updated simultaneously by multiple trans-
actions. These transactions are not necessarily restricted to database transactions only; they also include long
transactions.
Long transactions are the transactions where data fetch and data update are performed as separate database trans-
actions.
To illustrate a specific example, the transactions are observed in an application where the fetched data is displayed
on the Edit screen and the value edited on the screen is updated in database.
This chapter explains about exclusive control for the data stored in the database.
However, it should also be performed in a similar way for the data stored in data-store apart from database (such
as memory, files etc.).
To understand why exclusive control is necessary, let us first see the issues that occur in the absence of exclusive
control using the examples given below.
Problem 1
See the example below of receiving an order for Tea from users on a shopping site.
- 〇 User B orders 5nos. of Tea. Stock of Tea in the DB reduces by 5 and becomes 0.
3.
〇 - User A orders 5nos. of Tea. Stock of Tea in the DB reduces by 5 and becomes -5.
4.
Order from User A is accepted however an apology is conveyed due to non-availability of actual
stock.
The stock quantity of Tea stored in the table also shows a value (minus value) different from actual
stock quantity of Tea.
Problem 2
See the example below wherein the staff that manages the stock quantity of Tea on shopping site, displays the
stock quantity of Tea, calculates the added stock quantity at Client side and updates the stock quantity of Tea.
- 〇 Staff B adds stock of 10nos. of Tea and updates the stock quantity on Client side as
3.
5+10=15.
〇 - Staff A adds stock of 20nos. of Tea and updates the stock quantity on Client side as
4.
5+20=25.
The stock of tea 10nos. added in process 3 is lost and there is a mismatch in actual stock quantity
(35nos.).
Problem 3
See the example below wherein the data locked by batch processing is updated by online processing.
〇 - User A searches the updated information. Since batch is not committed at this point,
2.
the information prior to batch update can be fetched.
〇 - The update process for which user A was waiting can now be executed.
5.
User A executes update process after waiting for the batch to release the lock. However, data
fetched by User A prior to waiting is the data before batch update and batch processing may
overwrite the data with the updated data.
Batch takes a longer time as compared to online processing and user has to wait longer.
The easiest way to resolve all the 3 problems given in Necessity of exclusive control is to sequentially execute the
database processes.
When the processes are sequentially executed, there is no effect on transactions.
However, when the processes are sequentially executed, the number of transactions that can be executed in a unit
of time shows reduction resulting in deterioration in the performance.
In ANSI/ISO SQL standards, the guidelines indicating the isolation level (extent of impact by each transaction)
are defined. The 4 isolation levels of transaction are given below. Events that occur at each isolation level are
explained below.
1. READ
UNCOMMITTED
No Yes Yes
2. READ COMMITTED
No No Yes
3. REPEATABLE
READ
No No No
4. SERIALIZABLE
Dirty Read occurs when the data written by uncommitted transaction is read by other transaction.
When the same record is likely to be read twice in the same transaction, if other transaction is committed during
the first read and second read, the details read at first time and those at second time may differ. The multiple data
readings may vary based on commit timing of other transaction.
In Phantom Read, when a same record is being read twice in the same transaction, if other transaction adds or
deletes the record, it causes difference in the number of records (details) fetched during the first read and second
read.
The relationship between level of isolation and degree of concurrency between transactions is a Trade-off
relationship.
In other words, high isolation level leads to reduction in concurrency and vice versa.
Thus, it is necessary to balance the level of isolation and degree of concurrency of transactions in accordance
with application requirements.
The supported isolation level differs depending on the database to be used, it is necessary to understand the
characteristics of the database to be used.
Isolation levels supported by each database and their default values are shown below.
1. Oracle × 〇 (default) × 〇
2. PostgreSQL × 〇 (default) × 〇
3. DB2 〇 〇 (default) 〇 〇
When a balance is to be maintained between isolation and concurrency while maintaining data consistency,
it is necessary to perform exclusive control using Database Locking which is described below.
It is necessary to lock the data to be updated using an appropriate method due to the following reasons:
The three types of methods to lock the data stored in the database are as follows:
The Architect should adequately understand the characteristics of such locking and use the appropriate locking
method in accordance with the characteristics of application.
Optimistic
2. • When the already fetched data is be- • Ensures that the fetched data is
locking
ing updated by other transaction and not updated by other transaction.
if the updated contents need to be ver- • Column to manage versions
ified. needs to be defined in the table.
• When concurrency for the same data
is less and update process also takes
less time.
Pessimistic
3. • When the data that is likely to remain • Possibility of a process failure
locking
in locked state for a longer period is due to process results of other
updated. transaction is eliminated.
• When data consistency check needs to • Costly since it is necessary to ex-
be carried out since optimistic locking ecute SELECT statement for ob-
cannot be used (column cannot be de- taining pessimistic lock.
fined to manage versions).
• When concurrency for the same data
is more and update process takes
longer time.
The Architect should decide the type of locking to be used based on the functional and performance requirements.
• Optimistic locking is necessary to ensure that the database transactions such as returning and changing the
data on screen are cut off and the data remains unchanged in subsequent transaction.
• When locking is needed in a single transaction, both pessimistic and optimistic locking can be implemented;
however when pessimistic locking is used, lock is implemented at database level thus resulting in the
possible increase in database processing cost. It is always preferable to use optimistic locking unless there
• If optimistic locking is used in a process with higher update frequency wherein multiple tables are to
be updated in a single transaction, the waiting time for obtaining a lock can be minimized. However
the possibility of error occurrences increases since an exclusive error may occur in between the process.
If pessimistic locking is used, the waiting time for obtaining a lock is likely to increase; however since
exclusive error does not occur once lock is obtained, it reduces the possibility of error occurrences.
In actual application development, there could also be cases where exclusive control is necessary for the transac-
tions at business flow level. A typical example of business flow level transactions could be an application used at
a travel agency for making reservations while talking to the customer.
While making travel reservations, means of transport such as railway, accommodation facilities and additional
scheme, etc. are discussed. At this point, the reserved accommodation and additional scheme should remain
unavailable for other users. In such a case, the status of table should be updated from ‘Temporarily Reserved’
to ‘Reserved’. Even if the reservation is in process, other users should not be able to make the corresponding
reservation.
Description of exclusive control for business transaction is skipped in this chapter since it should be analyzed and
designed under business process design or functional design.
In most databases, when a record is updated (UPDATE, DELETE), a row lock is obtained to prevent updates by
other transactions till the primary transaction is committed or rolled back.
Thus, if the number of updated records is as anticipated, the data consistency can be ensured.
Exclusive control can be performed by using this characteristic and specifying the conditions to ensure data
consistency for WHERE clause at the time of update.
The support status of row lock at the time of update for each database is shown below.
Exclusive control using row lock function of database can be used when it is not necessary to verify the contents
updated by other transaction.
For example, it can be used in the purchase process of a shopping site wherein the purchased product quantity is
deducted from the records that manage product stock quantity.
Exclusive control is not recommended in the processes used for status management since the earlier status is
important in such processes.
• On a shopping site, both User A and User B are displayed a purchase screen of the same product at the
same time. A stock quantity fetched from Stock Table is also displayed at the same time.
• 5 products were purchased at the same time; but since User A clicked “Purchase” button a bit earlier, User
A buys the product first and then User B.
〇 - User A purchases 5 products of ItemId=01. 5nos. are deducted from Stock Table.
3.
Update from Stock set quantity = quantity - 5
where ItemId='01' and quantity >= 5
- 〇 User B purchases 5 products of ItemId=01. The system tries to deduct 5 products from
4.
Stock Table; however since transaction of User A is not yet completed, the purchase
process of User B is kept on hold.
〇 - Transaction of user A is committed.
5.
- 〇
6.
The transaction of user A is now committed, hence purchase for User B which was
kept on hold in step 4 is now resumed.
If a stock screen is viewed at this point, the stock quantity is now 95 instead of 100.
However, since the balance stock quantity is more than the purchase quantity (in the
1436 6 Data Access
above example, 5), 5 is deducted from Stock Table.
Note: Important
It is important to specify the deduction ( "quantity - 5" ) and update condition ( "and quantity
>= 5" ) in SQL.
In the similar scenario as above, if the stock quantity when a product purchase screen is displayed is 9, the stock
quantity when the User B resumes update process becomes 4. As it does not satisfy quantity >= 5condition,
update count becomes 0.
If the update count in the application is 0, purchase is rolled back and User B is prompted for re-execution.
Note: Important
It is important to verify the update count in the application and an error should occur if it is different from
the expected count and transaction should be rolled back.
When this method is used for locking, the process can be continued depending on conditions even if there is
a change in the referred information and data consistency can be ensured by using database function.
Optimistic locking is a method for ensuring data consistency wherein the data is not locked for transaction and is
updated only after checking whether it is same as fetched data.
When optimistic locking is to be used, a Version column for managing versions is created to determine if the data
to be updated is same as fetched data.
Data consistency can be ensured by keeping both the versions at the time of data fetch and data update same as a
condition for update.
This column is used in optimistic locking for managing the update count of a record. It is set to 0 when a record
is inserted and then it is incremented with each successful update. The Version column can be also be substituted
with the latest update timestamp in place of a number. However, when timestamp is used, uniqueness when
processes are executed simultaneously cannot be ensured. Hence, in order to ensure uniqueness, it is necessary to
use a number in Version column.
Exclusive control with optimistic locking is used when it is necessary to verify the contents updated by other
transaction.
For example, consider a case of workflow application wherein an applicant and an approver perform concurrent
operations (withdrawal and approval).
In this case, using exclusive control with optimistic locking, it is possible to notify the applicant and the approver
that the operation is yet to be completed since the status before and after the operation are different.
Warning: When an optimistic locking is to be performed, it is not appropriate to update/delete the record by
adding conditions other than ID and Version. This is because when the data cannot be updated, it is difficult
to determine whether the reason for update failure is version mismatch or condition mismatch. When the
conditions for update are different, it is necessary to check whether previous process meets all the conditions.
• Staff (Staff A, Staff B) who manage the stock quantity of shopping site add the product stock. Let us
assume that Staff A adds 5nos. and Staff B adds 15nos.
• A stock management screen is displayed in order to reflect the added stock in the stock management system.
The stock quantity being stored in Stock Management System is then displayed.
• Against the displayed stock quantity, the value calculated by summing up the quantity added by the staff is
entered in the Update form and the total stock is updated.
- 〇 Staff B displays stock management screen of the product. The stock quantity of 10nos.
2.
is displayed on the screen. Version of the referred data is 1.
〇 - Staff A adds the stock of 5 nos. against the stock quantity of 10 nos. displayed on the
3.
screen and updates stock quantity as 15nos. Version of the referred data is included
as an update condition.
UPDATE Stock SET quantity = 15, version = version + 1
WHERE itemId = '01' and version = 1
- 〇 Although Staff B adds 15nos. to the stock quantity of 10 nos. displayed on the screen
4.
and attempts to update the stock quantity to 25 nos.; however the transaction is kept
on hold since the transaction of Staff A is not completed yet. Version of the referred
data is included as an update condition.
〇 - Transaction of Staff A is committed. Version becomes 2 at this point.
5.
〇 - Update process of Staff B which was kept on hold in step 4 is now resumed since the
6.
transaction of Staff A is committed. At this time, since the version of Stock Table
data is 2, update result is 0 records. When the update result is 0 records, an exclusive
error occurs.
UPDATE Stock SET quantity = 25, version = version + 1
WHERE itemId = '01' and version = 1
Note: Points
Version ("version + 1") should be incremented and update condition ("and version = 1") should be
specified in SQL.
Pessimistic locking is a method to lock the data to be updated at the time of fetching so that it is not updated by
other transaction.
When pessimistic locking is to be used, lock is obtained for the record to be updated immediately after starting a
transaction.
Since the locked record is not updated by other transaction till the transaction is committed or rolled back, data
At the time of obtaining a pessimistic lock, if a lock is obtained by some other transaction, then the expected
behavior is specified as an option in some cases. In case of Oracle,
• Default value is select for update [wait], and it waits till lock is released.
• If set to select for update nowait, it immediately gives a resource busy error, if locked by other
transaction.
• If set to select for update wait 5, it waits for 5 seconds, and gives a resource busy error, if the
lock is not released within 5 seconds.
Although there are variations in the functions as per DB, it is necessary to analyze the method to be used when
using pessimistic locking.
Although the method of fetching a pessimistic lock varies for each database, such differences are absorbed by JPA
(Hibernate). Refer to Hibernate Developer Guide for RDBMS that supports Hibernate.
Exclusive control with pessimistic locking is used when it is applicable to any of the 3 cases given below.
• Batch processing has already started execution, and data to be updated online is locked by pessimistic
locking.
• Timeout period of 10 seconds is specified for the online processing and lock is obtained for the data to be
updated.
〇 - Online processing tries to perform pessimistic locking for the data to be updated;
2.
however it is kept on hold since the pessimistic locking is performed by the transaction
of batch processing.
SELECT * FROM Stock WHERE quantity < 5 FOR UPDATE WAIT 10
The flow given below illustrates a case wherein pessimistic lock is being obtained by other transaction in case of
“pessimistic lock no wait” setting.
An exclusive error occurs immediately without waiting for the release of pessimistic lock.
When there is a possibility of conflict between batch processing and online processing and if batch process-
ing is going to take longer time, it is recommended to specify timeout period of pessimistic exclusive locking.
The timeout period should be determined based on the online processing requirements.
Prevention of deadlock
When using database locks, it should be noted that if multiple records are updated in same transaction, 2
deadlocks shown below are likely to occur.
• Deadlock in table
Deadlock in table
This deadlock occurs when the records of the same table are locked by multiple transactions as shown in the flow
of (1)-(5) below.
(1)
(2)
〇 - Program A tries to obtain the lock for Record Y that is locked by the transaction of
Program B, however since lock of (2) is not released, the status is changed to “Waiting
(3)
for release”.
- 〇 Program B tries to obtain the lock for Record X that is locked by the transaction of
Program A, however since lock of (1) is not released, the status is changed to “Waiting
(4)
for release”.
- - Since Program A and Program B both have the “Waiting for release” status for each
other, it results into a deadlock. When a deadlock occurs, it is detected by the database
(5)
and error is thrown.
The deadlock can be resolved by timeout or retry; however, it is important to determine the rules for the
update sequence of records in the same table. When rows are to be updated one by one, the rules such as
updating in ascending order of PK (PRIMARY KEY) should be set.
Let us say if both Program A and Program B follow the rule of starting the update from Record X, the
deadlock shown in figure Deadlock in table above no longer occurs.
This deadlock occurs when records of different tables are locked by multiple transactions as shown in the flow of
(1)-(5) below.
The basic concept is same as Deadlock in table.
(1)
(2)
〇 - Program A tries to obtain the lock for Record Y of Table B that is locked by the
transaction of Program B, however since lock of (2) is not released, the status is
(3)
changed to “Waiting for release”.
- 〇 Program B tries to obtain the lock of Record X of Table A that is locked by the
transaction of Program A, however since lock of (1) is not released, the status is
(4)
changed to “Waiting for release”.
- - Since Program A and Program B both have the “Waiting for release” status for each
other, it results into a deadlock. When a deadlock occurs, it is detected by the database
(5)
and error is thrown.
Although deadlocks can be resolved by a timeout or retry; it is important to define rules for update sequence
across the tables.
Let us say if both Program A and Program B follow the rule of starting the update from Table A, the deadlock
shown in figure Deadlock between tables above no longer occurs.
Warning: A deadlock might still occur due to sequence of locking records even after adopting either of the
methods as a precaution. The rules should be defined for the lock sequence of tables and records.
From here, implementation method of exclusive control using O/R Mapper is described.
When exclusive control is to be performed using a row lock function of RDBMS, the following should be consid-
ered in SQL.
• Define SQL wherein exclusive control using a row lock function of RDBMS becomes valid.
(3)
As an update condition, add “stock quantity should be greater than or equal to the order quantity
(quantity >= #{quantity}).
(4)
• Call the Repository method to safely update the data using row lock function of RDBMS.
// (5)
boolean updated = stockRepository.decrementQuantity(itemCode, quantityOfOrder);
// (6)
if (!updated) {
// (7)
ResultMessages messages = ResultMessages.error().add(ResultMessage
.fromText("Not enough stock. Please, change quantity."));
throw new BusinessException(messages);
}
(5)
Optimistic locking
// ...
(1)
(2)
Define a method to update data using optimistic lock function, in Repository interface.
In above example, a method to update records with specified entity details is defined. When
(3)
update is successful, true is returned.
(6)
(4)
• Call the Repository method to safely update the data using optimistic lock function.
// (5)
Stock stock = stockRepository.findOne(itemCode);
if (stock == null) {
ResultMessages messages = ResultMessages.error().add(ResultMessage
.fromText("Stock not found. itemCode : " + itemCode));
throw new ResourceNotFoundException(messages);
}
// (6)
stock.setQuantity(stock.getQuantity() + addedQuantity);
// (7)
(5)
Specify the value to be updated for the entity fetched in step (5).
In above example, procured stock quantity is added.
(6)
Call the update method of Repository interface to reflect the entity updated in step (5) in persis-
tence layer (DB).
(7)
When performing optimistic lock for long transactions, points given below should be noted.
Warning: When performing optimistic locking for long transactions, apart from checking version at
the time of update, it should also be checked at the time of fetching data.
stock.setQuantity(stock.getQuantity() + addedQuantity);
boolean updated = stockRepository.update(stock);
// ...
Following points should be noted when application uses optimistic lock function in combination with row lock
function of RDBMS.
Warning: In case of application in which a process to perform exclusive control using row lock func-
tion of RDBMS and a process to perform exclusive control using optimistic lock function of RDBMS
coexist, version should be updated (incremented) in SQL using row lock function of RDBMS.
If version is not updated in SQL that performs exclusive control using row lock function of RDBMS,
the data may get overwritten by SQL that performs exclusive control using optimistic lock function.
<update id="decrementQuantity">
<![CDATA[
UPDATE
m_stock
SET
quantity = quantity - #{quantity},
/* (10) */
version = version + 1
WHERE
item_code = #{itemCode}
AND
quantity >= #{quantity}
]]>
</update>
(10)
Pessimistic locking
When exclusive control is to be performed using a row lock function of RDBMS, Query method is added to
Repository interface for implementation.
For Query method, refer to Adding query method and Operating the entities of Persistence Layer directly.
• Repository interface
@Modifying
(1) When the stock quantity is equal to or more than the order quantity, JPQL is specified in Query
method to reduce stock quantity.
Since it is necessary to check the update count, int is specified as the return value of Query
method.
• Service
(2)
Determine the call results of Query method. In case of 0, the stock quantity becomes inadequate,
since update conditions are not satisfied.
(3)
(4) Store the message notifying “No stock” or “Not enough stock” to generate a business error.
The generated error should be handled appropriately in Controller as per the requirements.
In the above example, only business rules are checked while performing exclusive control;
hence when update conditions are not satisfied, it is treated as business error and not exclusive
error.
For error handling methods, refer to Coding Points (Controller).
(5)
Optimistic locking
• Entity
@Entity
@Table(name = "m_stock")
public class Stock implements Serializable {
@Id
@Column(name = "item_code")
private String itemCode;
@Version // (1)
private long version;
// ...
(1)
• Service
stock.setQuantity(newQuantity); // (3)
stockRepository.flush(); // (4)
(2)
Specify the value to be updated for the entity fetched in step (2).
(3)
(4) Reflect the changes of (3) in the persistence layer (DB). This process is usually not required
since it is performed for the description purpose.
Normally, it is reflected automatically when the transaction is committed.
In the above example, when the version held by the entity fetched in step (2) and the version
stored in persistence layer (DB) do not match, optimistic locking error
(org.springframework.dao.OptimisticLockingFailureException) occurs.
SQL that is executed while reflecting to persistence layer (DB) of step (4).
(5)
It is important to note the following points while performing optimistic locking for long transactions.
Warning: It is not sufficient to simply assign @Version annotation for optimistic locking which is to be
performed for long transactions. When optimistic locking is to be performed for long transactions, version
check should also be carried out while fetching the data to be updated, in addition to the check at the time of
update performed using JPA function.
• Service
stock.setQuantity(newQuantity);
stockRepository.flush();
(1)
(2) Compare the version of the entity fetched by a different database transaction in advance with
the latest version of persistence layer (DB) fetched in step (1).
If versions match, optimistic locking which uses @Version annotation becomes valid in
subsequent processes.
stockRepository.flush();
For example, even if the value of the version sent from the screen is overwritten, it is not reflected in entity.
Hence the exclusive control can no longer be appropriately performed.
When optimistic locking for long transactions becomes necessary in multiple processes, it is desirable to standard-
ize the processes of (1) ~ (3) described above. For standardization method, refer to How to add custom method
.
It is important to note the following points when both row lock function of RDBMS and optimistic locking function
are to be used.
Warning: Version must always be updated in Query method that uses row lock function of RDBMS in case
of applications wherein a process performing exclusive control using a row lock function of RDBMS and a
process performing exclusive control using optimistic locking function co-exist, for the same data.
If version is not updated in Query method that performs exclusive control using row lock function of RDBMS,
the contents updated by Query method may be overwritten by the process of a different transaction. Hence,
the exclusive control is not performed properly.
• Repository interface
@Modifying
@Query("UPDATE Stock s SET s.quantity = s.quantity - :quantity"
+ ", s.version = s.version + 1" // (1)
+ " WHERE s.itemCode = :itemCode"
+ " AND :quantity <= s.quantity")
public int decrementQuantity(@Param("itemCode") String itemCode,
@Param("quantity") int quantity);
Pessimistic locking
• Repository interface
@Lock(LockModeType.PESSIMISTIC_WRITE) // (1)
@Query("SELECT s FROM Stock s WHERE s.itemCode = :itemCode")
Stock findOneForUpdate(@Param("itemCode") String itemCode);
-- (2)
SELECT
stock0_.item_code AS item1_5_
,stock0_.quantity AS quantity2_5_
,stock0_.version AS version3_5_
FROM
m_stock stock0_
WHERE
stock0_.item_code = 'ITM0000001'
FOR UPDATE;
(1)
Executed SQL. In the above example, the SQL executed while using PostgreSQL is given.
(2)
The types of pessimistic locking that can be specified using @Lock annotation are as given below.
PESSIMISTIC_WRITE
2.
Pessimistic lock for update is obtained and an select ... for update
exclusive lock is applied.
In case of exclusive lock, if a lock has already
been applied, the entity is fetched after waiting
for the lock to be released.
Lock is released at the time of commit or
rollback
PESSIMISTIC_FORCE_INCREMENT
3.
Exclusive lock is applied to the target data select ... for update +
when entity is fetched. Version is forcibly update
updated immediately after fetching the entity.
Lock is released at the time of commit or
rollback
There are 2 methods for specifying the locking timeout period: 1. Method wherein it is specified for the entire
process 2. Method wherein it is specified for each Query.
• xxx-infra.xml
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="packagesToScan" value="xxxxxx.yyyyyy.zzzzzz.domain.model" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="jpaPropertyMap">
<util:map>
<!-- ... -->
<entry key="javax.persistence.lock.timeout" value="1000" /> <!-- (1) -->
</util:map>
</property>
</bean>
(1)
When 0 is specified for Oracle and PostgreSQL, nowaitis added, and when locked by another transaction,
an exclusive error occurs without waiting for release of lock.
• Repository interface
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "2000")) // (1)
@Query("SELECT s FROM Stock s WHERE s.itemCode = :itemCode")
Stock findOneForUpdate(@Param("itemCode") String itemCode);
The handling method varies with the operation specifications of application when optimistic locking error occurs.
When there is no need to change the operation at request level, it is handled by using @ExceptionHandler
annotation.
@ExceptionHandler(OptimisticLockingFailureException.class) // (1)
public ModelAndView handleOptimisticLockingFailureException(
OptimisticLockingFailureException e) {
// (2)
ExtendedModelMap modelMap = new ExtendedModelMap();
ResultMessages resultMessages = ResultMessages.warning();
resultMessages.add(ResultMessage.fromText("Other user updated!!"));
modelMap.addAttribute(setUpForm());
modelMap.addAttribute(resultMessages);
String viewName = top(modelMap);
return new ModelAndView(viewName, modelMap);
}
(2) Carry out error handling. Generate the message to notify error and information required for
screen display (form or other model) and return ModelAndView specifying the destination.
For details on error handling, refer to Method to handle exception at use case level.
If there is a need to change the operation at request level, it is to be handled using try - catch in the handler
method of Controller.
// ...
try {
stockService.update(...);
} catch (OptimisticLockingFailureException e) { // (1)
// (2)
ResultMessages resultMessages = ResultMessages.warn();
resultMessages.add(ResultMessage.fromText("Other user updated!!"));
model.addAttribute(resultMessages);
return updateRedo(modelMap);
}
// ...
(1)
(2) Carry out error handling. Generate the message to notify error and information required for
screen display (form or other model) and return the destination view name.
For details on error handling, refer to Method to handle exception at use case level.
The handling method varies with the operation specifications of the application when pessimistic locking error
occurs.
If there is no need to change the operation at request level, it is handled using @ExceptionHandler annotation.
@ExceptionHandler(PessimisticLockingFailureException.class) // (1)
public ModelAndView handlePessimisticLockingFailureException(
PessimisticLockingFailureException e) {
// (2)
ExtendedModelMap modelMap = new ExtendedModelMap();
ResultMessages resultMessages = ResultMessages.warning();
resultMessages.add(ResultMessage.fromText("Other user updated!!"));
modelMap.addAttribute(setUpForm());
modelMap.addAttribute(resultMessages);
String viewName = top(modelMap);
return new ModelAndView(viewName, modelMap);
}
(2) Carry out error handling. Generate the message to notify error and information required for
screen display (form or other model) and return ModelAndView specifying the destination.
For details on error handling, refer to Method to handle exception at use case level.
If there is need to change the operation at request level, it is to be handled using try - catch in the handler
method of Controller.
// ...
try {
stockService.update(...);
} catch (PessimisticLockingFailureException e) { // (1)
// (2)
ResultMessages resultMessages = ResultMessages.warn();
resultMessages.add(ResultMessage.fromText("Other user updated!!"));
model.addAttribute(resultMessages);
return updateRedo(modelMap);
}
// ...
(1)
(2) Carry out error handling. Generate the message to notify error and information required for
screen display (form or other model) and return destination view name.
For details on error handling, refer to Method to handle exception at use case level.
7.1 Logging
Note: This contents described in this chapter are just guidelines that can be customized as per the business
requirements.
7.1.1 Overview
When a system is being operated, logs and messages are output as information sources to analyze the business
process usage and
to determine the causes when the system is down or when a business error etc. occurs.
It is important to output the log since effectiveness of analysis improves significantly if it is output at the time of
debugging.
If only the behavior is to be checked, it can be done by debugging in IDE or through a simple output such as
System.out.println.
However, if the output result is not manually stored, it is not possible to check the results later. This hampers the
effectiveness of analysis.
Obtaining the log by implementing logging library is just writing the code to be output.
Log can be verified at any convenient time later on.
Considering operating time, trail and analysis, it is recommended to implement logging library.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
There are various log output methods in Java and a number of methods can be selected; however this guideline
recommends SLF4J (interface) + Logback (implementation)
due to simplicity of coding, ease of modification and performance.
Types of Logs
Measurement of request processing Process start and end time, process elapsed
time time (ms),
(Should not be output during Information that can identify the execution
production environment operations) process etc. (execution controller + method,
request URL etc.).
Assessing business process volume Information that can identify access date and
time, user (IP address, authentication
information)
Information that can identify execution
process (request URL) etc., information for
leaving a trail
INFO External com- Send and receive time, send and receive data
munication etc.
Analysis of error that occurs while
log
communicating with external system
Debug log, Access log, External communication log, Business error log and System error log are output to same
file.
In this guideline, the log file that outputs the above mentioned logs is called as application log.
Note: The sequence of log levels of SLF4J or Logback is TRACE < DEBUG < INFO < WARN < ERROR. It
does not include FATAL level provided in commons-loggins or Log4J.
It is important to note the points given below for the output contents of log.
1. ID to be output to log
When log is to be monitored during the operation, it is recommended to include a message ID in the log to
be monitored.
Further, when the business process volume is to be assessed using an access log, the log should be output
according to the ID assigned to each business process as explained in Message Management.
This facilitates overall data compilation.
Note: The readability of log is enhanced by including an ID in the log thereby reducing the time required
for primary isolation of failure analysis. Refer to Message Management for log ID structure. However, there
is no need to assign an ID to all the logs. ID is not required at the time of debugging. It is recommended
when the system is operational so as to isolate the log quickly.
During failure, when a system user is notified by displaying a log ID (or a message ID) on the error screen
and the ID is then notified to the call center, for that user, failure analysis becomes easier.
However, note that the vulnerabilities of the system may be exposed if errors are displayed on the screen
along with the failure details.
In common library, the mechanism(component) is provided to include the message ID(exception code) into
the log and the screen when an exception is occurred. Details refer to “Exception Handling”.
2. Traceability
To improve the traceability, it is recommended to output a unique track ID (hereafter referred to as
X-Track) at request level in each log.
Example of logs including X-Track is given below.
Logs can be linked together using Track IDs even when the output is irregular.
In the above example, it can be clearly understood that 4th, 8th and 9th rows in the log are pertaining to
the same request.
In common library, org.terasoluna.gfw.web.logging.mdc.XTrackMDCPutFilter to be
added to MDC is provided by generating a unique key for each request.
XTrackMDCPutFilter sets Track ID in “X-Track” of HTTP response header as well. X-Track is used
as a Track ID label in the log.
Refer to About MDC for the usage methods.
3. Log mask
If personal information, credit card number etc. are output to the log file as is, the information that has
security threat should be masked if needed.
Performance log The processing time of business process is measured and it is output after executing business
process. Request processing time is measured and log is output when response is returned.
It is usually implemented in AOP or Servlet filter.
Debug log When it is necessary to output debug information at the time of development, a suitable log
output process is implemented in source code.
Access log INFO log is output at the time of receiving a request and returning the response.
It is usually implemented in AOP or Servlet filter.
External INFO log is output before and after external system linking.
communication
log
Business error WARN log is output when business process exception is thrown.
log It is usually implemented in AOP.
System error log An ERROR log is output when system exception or unexpected exception occurs.
It is usually implemented in AOP or Servlet filter.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Note that when the log is output, the contents should not be exactly identical to other logs. This is helpful
in easily identifying the location of the output.
1. Settings of Logback
Settings of Logback
Logback settings are described in logback.xml under the class path. An example of configuration is shown below.
Refer to Logback Official Manual -Logback Configuration- for the detailed configuration of logback.xml.
Note: Settings of Logback are read automatically as per the rules given below.
5. If class which implements Configurator interface is not found, settings of BasicConfigurator class
(console output)
In this guideline, it is recommended to place logback.xml in the class path. Moreover, apart from automatic read-
ing, it is possible to read programmatically through an APIand specify the configuration file in system properties.
logback.xml
</logger>
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<logger name="jdbc.sqltiming">
<level value="debug" />
</logger>
</configuration>
(2) The output format for log is specified. If the format is not specified, the message alone is output.
Time and message level etc. are output according to the business requirements.
Here, LTSV (Labeled Tab Separated Value) of “Label:Value<TAB>Label:Value<TAB>...” format is
set.
(4) A current file name (File name of log being output) is specified. It should be specified when it is
necessary to specify a fixed file name.
If <file>log file name</file> is not specified, it is output with the name in pattern (5).
(5) Name of the file after rotation is specified. Usually, date or time format is widely used.
Note that 24 hour clock is not set if HH is mistakenly set as hh.
(6) The number of remaining files for which rotation is performed is specified.
7.1.
(9) Logging The logger name is set so that logger under com.example.sample outputs the log above debug
1477
level.
LTSV is one of text data formats, and mainly used as the log format.
For log fomart, LTSV is easy to parse using some tools because it has following features.
• It’s easy to split the field compared to other delimiters because tabs is used as field delimiters.
• Even if the field definition (changing the position of field or adding the field or removing the field) is
changed, it does not affect to parsing because of including a label(name) in the field.
It is also one of features that there are that pasting on the Excel can format it with the least effort.
Type Overview
appender “Location” and “Layout” to be used for output
root Default “Appender” and the minimum “Log level” to be used for output
logger “Which logger (package or class etc.)” is to be output at which minimum “log level”
In <appender> element, the “location” and the “layout” to be used for output are defined. It is not used at the time
of the log output only by defining the appender. It is used for the first time when it is referred in <logger> element
or <root> element. There are two attributes, namely, “name” and “class” and both are mandatory.
Attribute Overview
name Name of the appender. It is specified by appender-ref. Any name can be assigned as there
is no restriction on assigning the name.
class FQCN of appender implementation class.
Appender Overview
ConsoleAppender Console output
FileAppender File output
RollingFileAppender File output (Rolling possible)
AsyncAppender Asynchronous output. It is used for logging in processes with high performance
requirement. (It is necessary to set the output destination in other Appender.)
Log is output by calling a method according to each log level of SLF4J logger(org.slf4j.Logger).
package com.example.sample.app.welcome;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HomeController {
Log output results are shown below. Log level of com.example.sample is DEBUG, hence TRACE log is not
output.
The description can be as given below when an argument is to be entered in placeholder of a log message.
int a = 1;
logger.debug("a={}", a);
String b = "bbb";
logger.debug("a={}, b={}", a, b);
Warning: Note that string concatenation such as logger.debug("a=" + a + " , b=" + b);
should not be carried out.
When the exception is to be caught, ERROR log (WARN log in some cases) is output as shown below. Error
message and exception generated are passed to the log method.
try {
throwException();
} catch (Exception e) {
logger.error("Exception happened!", e);
// omitted
}
// omitted
}
Accordingly, stack trace of caused exception is output and the cause of the error can be easily analyzed.
However, as shown below, when the exception that is caught is wrapped with other exception and is re-thrown at
upper level, there is no need to output the log. This is because usually the error log is output at upper level.
try {
throwException();
} catch (Exception e) {
throw new SystemException("e.ex.fw.9001", e);
// no need to log
}
Note: When cause exception is to be passed to a log method, a placeholder cannot be used. Only in this
case, the message argument can be concatenated using a string.
try {
throwException();
} catch (Exception e) {
SLF4J Logger internally checks the log level and outputs actual log only for the required levels.
Therefore, the log level check as given below is basically not necessary.
if (logger.isDebugEnabled()) {
logger.debug("This log is Debug.");
}
if (logger.isDebugEnabled()) {
logger.debug("a={}", a);
}
However, the log level should be checked in the cases given below to prevent performance degradation.
When arguments of log message are 3 or more, argument array should be passed in the API of SLF4J.
Log level should be checked in order to to avoid the cost for generating an array and the array should
be generated only when necessary.
if (logger.isDebugEnabled()) {
logger.debug("a={}, b={}, c={}", new Object[] { a, b, c });
}
When it is necessary to call a method while creating an argument for the log message, the log level
should be checked to avoid the method execution cost and the method should be executed only when
necessary.
if (logger.isDebugEnabled()) {
logger.debug("xxx={}", foo.getXxx());
}
Log output specifications are individually defined based on monitoring products and requirements and a case
which is individually implemented is assumed. Here, a couple of examples are described below.
An implementation example which aims to improve maintenance through uniform management of log messages.
Log messages are managed uniformly by consolidating the log messages in another file like a property file and
resolving messages at the time of log output.
A method is described as an implementation example wherein the messages corresponding to log ID are output
in the property file to enable setting of log ID in the argument of log output method.
Note: A method wherein consolidation is done using enum of Java also exists as a method to manage
Log ID and log message, however, a general method wherein the property file is used is introduced in this
guideline.
2. Property file
are created.
Here, LogIdBasedLoggeris considered as a Logger wrapper class and log-messages.propertiesis
considered as a property file.
package com.example.sample.common.logger;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.ResourceBundleMessageSource;
static { // (3)
messageSource.setDefaultEncoding("UTF-8"); // (4)
messageSource.setBasenames("i18n/log-messages"); // (5)
}
}
}
(1) A log message when the log ID is not defined. Here, a message same as
org.terasoluna.gfw.common.exception.ExceptionLoggeris used as an example.
(6) Use SLF4J in logger wrapper class as well. Logging library implementation is not used directly.
(8) In this implementation example, log ID is not used in the log of DEBUG level. Log message of
argument is output as it is.
(9) Output TRACE/INFO/WARN/ERROR level log by fetching the log message corresponding to log ID
1486 from the property7file.
General-purpose functions that do not depend on the application
(10) If log ID is not described in the property file while calling getMessage, an exception
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: In this guideline, since message for screen output and message for log output are managed sepa-
rately, a new property file is created. However, both the messages can be output in a single file.
File unit should be determined in accordance with the nature of the application and how to manage a method
of message.
• Call sample
package com.example.sample.app.welcome;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.example.sample.common.logger.LogIdBasedLogger;
@Controller
public class HomeController {
Output format of log messages differs according to log output format as given in the table below..
Hence, log output format must be combined with another format or both the formats must be integrated with
individual format in the integration of log format.
In this guideline, an example wherein the format is defined in the log output by business logic and an example
wherein both are integrated with the individual format ({Exception code (message ID) or log ID}], {message or
log message}) are explained.
(2) Framework detects the exception Business error log, system error [{Exception code (Message ID)}]
and implicitly outputs the log log etc {Message}
Note: “Business error log” and “system error log” output at the time of generating an exception are output in the
default format of the table given above in accordance with the exception handling system of Common library.
Integration with the format of the log output by framework after detecting an exception
An implementation example is shown wherein the log output by business logic is coordinated with the format of
the log output by framework after detecting an exception.
In this guideline, it is implemented by adding a process to apply format, in Logger wrapper class
(LogIdBasedLogger ).
package com.example.sample.common.logger;
// omitted
// omitted
// omitted
Sr.No. Description
(1) Add a process wherein a log message is created based on log message format.
An implementation example wherein the log output by business logic and framework is integrated in a unique
format ([{Exception code (message ID) or log ID}], {message or log message}).
An example wherein log output by business logic is output in the format mentioned earlier.
In this guideline, it is implemented by adding a process to apply the format, to Logger wrapper class
(LogIdBasedLogger ).
package com.example.sample.common.logger;
// omitted
// omitted
// omitted
(1) Add a process to create log message based on log message format.
An example wherein the log output by framework is output in format mentioned earlier.
Bean definition of ExceptionLoggerof applicationContext.xmlis changed to change the format of
business error log and system error log.
An example of how to define ExceptionLoggeris given below.
• applicationContext.xml
7.1.4 Appendix
Using MDC
MDC internally consists of a ThreadLocal map and sets value for the key. The value set in log can be output till it
is removed.
The value should be set at the beginning of the request and removed at the time of process termination.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
The value added to MDC can be output in log by defining the output format as %X{key name}format in
<pattern> of logback.xml.
<filter-mapping>
<filter-name>MDCClearFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
As shown in the sequence diagram below, MDCClearFilter should be defined prior to each
MDCPutFilterto clear the contents of MDC for post-processing.
Request ID and User ID can be output to log by adding %X{X-Track} and %X{USER} in <pattern> of
logback.xml.
<sec:http>
<!-- omitted -->
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER"/> <!-- (1) -->
<!-- omitted -->
</sec:http>
HttpSessionEventLoggingListener
In logback.xml, org.terasoluna.gfw.web.logging.HttpSessionEventLoggingListener is
set at debug level as shown below.
<logger
name="org.terasoluna.gfw.web.logging.HttpSessionEventLoggingListener"> <!-- (1) -->
<level value="debug" />
</logger>
When the lifecycle of an object is managed using a Session such as @SessionAttributes etc., it is strongly
recommended to use this listener to confirm whether the attributes added to the session are deleted as anticipated.
TraceLoggingInterceptor
org.terasoluna.gfw.web.logging.TraceLoggingInterceptor is the
HandlerInterceptor to output start and termination of Controller process to the log. When the pro-
cess is terminated, View name returned by the Controller, attributes added to Model and the time required for
Controller process are also output.
<mvc:interceptors>
<!-- omitted -->
<mvc:interceptor>
<mvc:mapping path="/**" />
By default, WARN log is output if Controller process takes more than 3 seconds.
When the threshold value is to be changed, it is specified in nano seconds in warnHandlingNanos property.
The following settings should be performed if the threshold value is to be changed to 10 seconds (10 * 1000 *
1000 * 1000 nano seconds).
<mvc:interceptors>
<!-- omitted -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor">
<property name="warnHandlingNanos" value="#{10 * 1000 * 1000 * 1000}" />
</bean>
</mvc:interceptor>
<!-- omitted -->
</mvc:interceptors>
ExceptionLogger
7.2.1 Overview
Value that needs to be managed as properties can be classified into following two categories.
Note: In this guideline, it is recommended to manage these settings as properties (properties file).
If an application is mechanized such a way that acquires setting from the properties, there is no need to re-build
the application even if there is any changes in these values. Therefore it is possible to release the tested application
on production environment.
Tip: Values that are managed as properties can be acquired from JVM system properties (-D option) or OS
environment variables. About access order, refer to “How to use”.
Values that are managed as properties can be used at the following two locations.
Properties file values in Java class and bean definition file can be accessed by defining
<context:property-placeholder/> tag in bean definition file.
<context:property-placeholder/> tag reads the group of specified properties files
and can fetch values for properties files key xxx specified in ${xxx} format in @Value annotation or bean
definition files.
Note: When specified in ${xxx:defaultValue}format and when key xxx is not set in properties
file, defaultValue is used.
• applicationContext.xml
• spring-mvc.xml
2. Environment variables
As per default setting, properties file defined in application is searched and read after all environment related
properties (JVM system properties and environment variables) are read.
Read sequence can be changed by setting local-override attribute of
<context:property-placeholder/> tag to true.
By performing these settings, the properties defined in application are enabled with higher priority.
<context:property-placeholder
location="classpath*:META-INF/spring/*.properties"
local-override="true" /> <!-- (1) -->
(1) Access properties in the following order when local-override attribute is set to true.
1. Application definition properties
2. System properties of active JVM
3. Environment variables
<context:property-placeholder
location="classpath:/META-INF/property/extendPropertySources.properties"
order="1" ignore-unresolvable="true" /> <!-- (1) -->
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties"
order="2" ignore-unresolvable="true" /> <!-- (2) -->
(1) By setting the order attribute to a value which is less than (2), properties file corresponding
to location attribute is read before (2).
When a key overlapping with the key in properties file read in (2) exists, value fetched in
(1) is given preference.
By setting ignore-unresolvable attribute to true, error which occurs when key exists only in
properties file of (2) can be prevented.
(2) By setting the order attribute to value greater than (1), properties file corresponding to
location attribute is read after (1).
When a key overlapping with the key in properties file read in (1) exists, value fetched in
(1) is set.
By setting ignore-unresolvable attribute to true, error which occurs when key exists only in
properties file of (1) can be prevented.
Properties file
database.url=jdbc:postgresql://localhost:5432/shopping
database.password=postgres
database.username=postgres
database.driverClassName=org.postgresql.Driver
<bean id="dataSource"
destroy-method="close"
class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName"
value="${database.driverClassName}"/> <!-- (1) -->
<property name="url" value="${database.url}"/> <!-- (2) -->
<property name="username" value="${database.username}"/> <!-- (3) -->
<property name="password" value="${database.password}"/> <!-- (4) -->
<!-- omitted -->
</bean>
(1) By setting ${database.driverClassName}, the value for read properties file key
database.driverClassName gets substituted.
(2) By setting ${database.url}, the value for read properties file key database.url gets
substituted.
(3) By setting ${database.username}, the value for read properties file key
database.username gets substituted.
(4) By setting ${database.password}, the value for read properties file key
database.password gets substituted.
As a result of reading the properties file key, the values are replaced as follows:
<bean id="dataSource"
destroy-method="close"
class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url"
value="jdbc:postgresql://localhost:5432/shopping"/>
<property name="username" value="postgres"/>
<property name="password" value="postgres"/>
<!-- omitted -->
</bean>
It is possible to use properties in Java class by specifying @Value annotation in the field wherein properties
values are to be stored.
To use @Value annotation, the corresponding object needs to be stored in DI container of Spring.
Properties file
Java class
@Value("${item.upload.title}") // (1)
private String uploadTitle;
@Value("${item.upload.dir}") // (2)
private Resource uploadDir;
@Value("${item.upload.maxUpdateFileNum}") // (3)
private int maxUpdateFileNum;
(1) By setting ${item.upload.title} to @Value annotation value, the value for read
properties file key item.upload.title gets substituted.
uploadTitle is substituted by “list of update file” in String class.
(2) By setting ${item.upload.dir} to @Value annotation value, the value for read
properties file key item.upload.dir gets substituted.
org.springframework.core.io.Resource object created with initial value
“/tmp/upload” is stored in uploadDir.
Warning: There could be cases wherein properties values are to be used in static methods of Utility
classes etc.; however properties value cannot be fetched using @Value annotation in classes for which
bean definition is not done. In this case, it is recommended to create Helper class with @Component
annotation and to fetch properties values using @Value annotation. (This class needs to be included
in the component-scan scope.) Classes in which values from properties file is to be used, should not be
made Utility classes.
Extension of method for fetching properties values is explained below. This can be achieved by extend-
ing org.springframework.context.support.PropertySourcesPlaceholderConfigurer
class.
The example below illustrates a case wherein encrypted properties file is used.
• applicationContext.xml
• spring-mvc.xml
(2) Set “locations” in name attribute of property tag and specify the path of the properties file to be
read, in value attribute.
The method of specifying path of the properties file to be read is same as About properties file
definition.
Java class
• Extended PropertySourcesPlaceholderConfigurer
• EncryptedValueResolver.java
@Override
public String resolveStringValue(String strVal) { // (3)
}
return value;
}
}
(5) Check whether values of properties file are encrypted. The method to determine whether the
values are encrypted differs depending on type of implementation.
Here, the value can be considered encrypted if it starts with “Encrypted:”.
If the values are encrypted, decrypt them in step (6) and if they are not encrypted, return them
as is.
(6) Encrypted values of properties file are being decrypted. (No specific decryption process is
mentioned.)
Decryption method differs depending on type of implementation.
@Value("${encrypted.property.string}") // (1)
private String testString;
@Value("${encrypted.property.int}") // (2)
private int testInt;
@Value("${encrypted.property.integer}") // (3)
private Integer testInteger;
@Value("${encrypted.property.file}") // (4)
private File testFile;
Properties file
The values encrypted as properties values are prefixed with “Encrypted:” to indicate that they are encrypted.
Although one can view the contents of properties file, but cannot understand them as the values are encrypted.
encrypted.property.string=Encrypted:ZlpbQRJRWlNAU1FGV0ASRVteXhJQVxJXXFFAS0JGV1Yc
encrypted.property.int=Encrypted:AwI=
encrypted.property.integer=Encrypted:AwICAgI=
encrypted.property.file=Encrypted:YkBdQldARkt/U1xTVVdfV1xGHFpGX14=
7.3.1 Overview
This guideline recommends the use of JSR-310 Date and Time API which offers various date and time
calculations,
as against java.util.Date and java.util.Calendar.
Note: Since JSR-310 Date and Time API has been introduced from Java8, using Joda Time is recom-
mended for the environments of versions Java8 and below. For how to use Joda Time, refer Date Operations
(Joda Time).
Date and Time API offers various classes depending on the application such as a class exclusively handling dates
and a class exclusively handling time.
In this guideline, the focus is on java.time.LocalDate , java.time.LocalTime and
java.time.LocalDateTime, however, since the prefix of all the methods offered by each class is same for
primary date and time operations, it should be interpreted by substituting with the appropriate class name.
Classes and methods used primarily are as given below.
Note: For the topics that are not covered in the guideline, refer Javadoc for details.
Note: Date and Time API class is immutable (date and time calculation result is considered as a new
object, and changes do not occur in the objects for calculation).
Specific date and time can be specified by using of method. Example is shown below.
// 23:30:59
LocalTime localTime = LocalTime.of(23, 30, 59);
// 2015/12/25
LocalDate localDate = LocalDate.of(2015, 12, 25);
// 2015/12/25 23:30:59
LocalDateTime localDateTime = LocalDateTime.of(2015, 12, 25, 23, 30, 59);
// LeapYear(2012/2)
LocalDate localDate1 = LocalDate.of(2012, 2, 1);
// Next monday(2012/2/6)
LocalDate localDate3 = localDate1.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
When an international application is to be created, a design must be adopted considering the time zone.
java.time.OffsetTime , java.time.OffsetDateTime and java.time.ZonedDateTime must
be used selectively in Date and Time API in accordance with the purpose for which it is to be used.
Example is given below.
1. Use java.time.OffsetTime when the time difference between time + UTC is to be fetched.
// Ex, 12:30:11.567+09:00
OffsetTime offsetTime = OffsetTime.now();
2. Use java.time.OffsetDateTime when the time difference between date, time + UTC is to be
fetched.
// Ex, 2015-12-25T12:30:11.567+09:00
OffsetDateTime offsetDateTime = OffsetDateTime.now();
3. Use java.time.ZonedDateTime when the time difference and region for date, time + UTC is to be
fetched.
// Ex, 2015-12-25T12:30:11.567+09:00[Asia/Tokyo]
ZonedDateTime zonedDateTime = ZonedDateTime.now();
Further, current date and time considering the time zone can be fetched in all these methods by specifying
java.time.ZoneId which indicates time zone in the argument.
java.time.ZoneId example is shown below.
Note that, java.time.ZoneId consists of a method to be defined by region name/area name format and a
method defined by time difference from UTC.
ZoneId.of("Asia/Tokyo");
ZoneId.of("UTC+01:00");
Time period
java.time.Period is used while handling date based period and java.time.Duration is used while
handling time based period.
Since day represented by java.time.Duration is of exactly 24 hours, expected results may not be obtained
if daylight saving changes are not incorporated.
On the contrary, since java.time.Period represents 1 day including daylight saving, an error does not
occur even in the system which handles daylight saving.
Example is given below.
Note: A method can also be employed wherein the period is generated by specifying it by using of
method. For details, refer Javadoc of Period, Duration.
Type conversion
// Ex. 12:10:30
LocalTime localTime = LocalTime.now();
// 2015-12-25 12:10:30
LocalDateTime localDateTime = localTime.atDate(LocalDate.of(2015, 12, 25));
// Ex. 2012-12-25
LocalDate localDate = LocalDate.now();
// 2015-12-25 12:10:30
LocalDateTime localDateTime = localDate.atTime(LocalTime.of(12, 10, 30));
// 12:10:30
LocalTime localTime = localDateTime.toLocalTime();
// 2012-12-25
LocalDate localDate = localDateTime.toLocalDate();
// Ex, 12:30:11.567+09:00
OffsetTime offsetTime = OffsetTime.now();
// 2015-12-25T12:30:11.567+09:00
OffsetDateTime OffsetDateTime = offsetTime.atDate(LocalDate.of(2015, 12, 25));
// Ex, 2015-12-25T12:30:11.567+09:00
OffsetDateTime offsetDateTime = OffsetDateTime.now();
// 2015-12-25T12:30:11.567+09:00[Asia/Tokyo]
ZonedDateTime zonedDateTime = offsetDateTime.atZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// Ex, 2015-12-25T12:30:11.567+09:00[Asia/Tokyo]
ZonedDateTime zonedDateTime = ZonedDateTime.now();
// 2015-12-25T12:30:11.567+09:00
OffsetDateTime offsetDateTime = zonedDateTime.toOffsetDateTime();
// 12:30:11.567+09:00
OffsetTime offsetTime = zonedDateTime.toOffsetDateTime().toOffsetTime();
// Ex, 12:30:11.567
LocalTime localTime = LocalTime.now();
// 12:30:11.567+09:00
OffsetTime offsetTime = localTime.atOffset(ZoneOffset.ofHours(9));
Besides, conversion to another class is also possible by adding missing information (date information is not
adequate in case of conversion from LocalTime to LocalDateTime).
Conversion method begins with the prefix at or to. For details, refer Javadoc of each class .
However, since a method which converts java.time.Instant offered by Date and Time API is added to
java.util.Date from Java8 and subsequent versions, a conversion can be carried out through
java.time.Instant.
Example is given below.
An upgrade is added to java.sql package from Java8 version and a method for mutual conversion with
java.time package is defined.
Example is given below.
Currently, Date Factory for Date and Time API is not provided in the common library. (Refer: System Date)
However, java.time.LocalDate can be generated by using
org.terasoluna.gfw.common.date.ClassicDateFactory and java.sql.Date as an interim
measure.
It can be generated by converting from java.time.LocalDate even for the java.time.LocalTime
and java.time.LocalDateTime classes.
Example is given below.
Java class
@Inject
ClassicDateFactory dateFactory;
// omitted
}
Note: Date Factory corresponding to Date and Time API will be added later.
In this case, Locale and ResolverStyle (strict) can be defined besides string format.
Since default value of Locale changes depending on the system, it should be set at the time of initialization.
Also, when ResolverStyle (strict) uses ofPattern method, ResolverStyle.SMART is set as default,
however, in this guideline, it is recommended to specify ResolverStyle.STRICT for strict interpretation of
date to avoid occurrence of unexpected behaviour.(When ISO pattern formatter is to be used,
ResolverStyle.STRICT is specified.)
Also, since format yyyy in Date and Time API represent year in the calendar, interpretation will be different
according to the calendar. (Year will be 2015 according to Western calendar but will be 0027 according to
Japanese calendar).
When western calendar is to be indicated, it is recommended to use uuuu format instead of yyyy format.For the
defined format list, refer DateTimeFormatter .
// "2015-12-25"
System.out.println(localDate1.toString());
// "20151225"
System.out.println(formatter1.format(localDate1));
// "Western calendar 2015/12/25 Friday"
System.out.println(formatter2.format(localDate1));
Controller class
@Controller
public class HomeController {
model.addAttribute("currentDate", localDate1.toString());
model.addAttribute("formattedCurrentDateString", dateFormatter.format(localDate1));
// omitted
}
}
jsp file
<p>currentDate = ${f:h(currentDate)}.</p>
<p>formattedCurrentDateString = ${f:h(formattedCurrentDateString)}.</p>
<p>currentDate = 2015-12-25.</p>
<p>formattedCurrentDateString = 2015/12/25.</p>
Similar to conversion to string, various date strings can be converted to Date and Time API class by using
java.time.fomat.DateTimeFormatter.
Example is as shown below.
Date operation
Date and time can be easily calculated and compared in Date and Time API.
Example is as shown below.
plus method and minus method are provided for calculating date and time.
Note: If a negative number is passed in the plus method, results similar to the results at the time of using
minus method can be obtained. Same for minus method.
Time series for past, future and current period can be compared in Date and Time API.
Example is as shown below.
daytime.isBefore(morning); // false
morning.isAfter(evening); // true
evening.equals(LocalTime.of(17, 30, 00)); // true
daytime.isBefore(daytime); // false
morning.isAfter(morning); // false
may.isBefore(june); // true
june.isAfter(july); // false
july.equals(may); // false
may.isBefore(may); // false
june.isAfter(june); // false
Note that, the class applicable to Interval of Joda Time currently does not exist in Date and Time API.
1. When a valid date and time string is to be determined, it can be determined based on the occurrence and
non-occurrence of java.time.format.DateTimeParseException.
try {
// DateTimeParseException
LocalTime localTime = LocalTime.parse(strDateTime, timeFormatter);
}
catch (DateTimeParseException e) {
System.out.println("Invalid time string !!");
}
try {
// DateTimeParseException
LocalDate localDate = LocalDate.parse(strDateTime, dateFormatter);
}
catch (DateTimeParseException e) {
System.out.println("Invalid date string !!");
}
date1.isLeapYear(); // true
date2.isLeapYear(); // false
When the respective year, month, day, hours, minutes and seconds are to be fetched, use get method.
An example to fetch information related to date is shown below.
// 2015
int year = localDate.getYear();
// 2
int month = localDate.getMonthValue();
// 1
int dayOfMonth = localDate.getDayOfMonth();
// 32 ( day of year )
int dayOfYear = localDate.getDayOfYear();
A class called java.time.chrono.JapaneseDate is provided in Date and Time API to handle Japanese
calendar.
Similar to java.time.LocalDate, Japanese calendar can be fetched by using now method and of method.
Further, Japanese calendar can also be fetched by using java.time.chrono.JapaneseEra class.
// DateTimeException
JapaneseDate japaneseDate = JapaneseDate.of(1500, 1, 1);
Conversion between Western and Japanese calendars can be easily carried out from java.time.LocalDate
by using from method.
7.4.1 Overview
The API of java.util.Date , java.util.Calendar class is poorly built to perform complex date
calculations.
This guideline recommends the usage of Joda Time which provides quality replacement for the Java Date and
Time classes.
The usage of Joda Time, Joda Time JSP tags is explained below.
Fetching date
2. Use org.joda.time.LocalDate when only date, which does not include time and TimeZone, is
required.
3. Use org.joda.time.LocalTime when only time, which does not include date and TimeZone, is
required.
For the list of other available Time zone ID strings, refer to Available Time Zones.
Fetching the date and time by specifying Year Month Day Hour Minute and Second
DateTime dateTime = new DateTime(year, month, day, hour, minute, second, millisecond);
DateTime provides a method to fetch Year and Month. The example is shown below.
Type conversion
String format
Date operations
Date calculations
LocalDate provides methods to perform date calculations. Examples are shown below.
(1) The value specified in argument of LocalDate#minusDays is subtracted from the date. In this
example, it becomes 2013-01-09.
(2) The value specified in argument of LocalDate#plusDays is added to the date. In this example, it
becomes 2013-01-11.
(3) The value specified in argument of LocalDate#plusMonths is added to the number of months. In this
example, it becomes 2013-04-10.
(4) The value specified in argument of LocalDate#plusYears is added to the number of years. In this
example, it becomes 2014-01-10.
For methods other than those mentioned above, refer to LocalDate JavaDoc .
The method of fetching the first and the last day of the month by considering the current date and time as base, is
shown below.
(1) Get Property object that holds the attribute values related to day of current month.
(2) Get first day of the month by fetching the minimum value from Property object. In this example, it
becomes 2013-01-01.
(3) Get last day of the month by fetching the maximum value from Property object. In this example, it
becomes 2013-01-31.
The method of fetching the first and the last day of the week by considering the current date and time as base, is
shown below.
(1) Get Property object that holds attribute values related to the day of current week.
(2) Get first day of the week (Monday) by fetching the minimum value from Property object. In this
example, it becomes 2013-01-07.
(3) Get last day of the week (Sunday) by fetching the maximum value from Property object. In this
example, it becomes 2013-01-13.
By comparing the date and time, it can be determined whether it is a past or future date.
System.out.println(dt1.isAfter(dt1)); // false
System.out.println(dt1.isAfter(dt2)); // false
System.out.println(dt1.isAfter(dt3)); // true
System.out.println(dt1.isBefore(dt1)); // false
System.out.println(dt1.isBefore(dt2)); // true
System.out.println(dt1.isBefore(dt3)); // false
System.out.println(dt1.isEqual(dt1)); // true
System.out.println(dt1.isEqual(dt2)); // false
System.out.println(dt1.isEqual(dt3)); // false
(1) isAfter method returns true when the target date and time is later than the date and time of
argument.
(2) isBefore method returns true when the target date and time is prior to the date and time of
argument.
(3) isEqual method returns true when the target date and time is same as the date and time of
argument.
Joda-Time provides several classes related to duration. The following 2 classes are explained here.
• org.joda.time.Interval
• org.joda.time.Period
Interval
interval1.contains(anyDate); // (1)
interval1.abuts(interval2); // (2)
interval1.gap(interval3); // (3)
interval1.overlap(interval4); // (4)
(1) Check whether the interval includes the specified date and interval, using Interval#contains method.
If the interval includes the specified date and interval, return “true”. If not, return “false”.
(2) Check whether the 2 intervals are consecutive, using Interval#abuts method.
If the 2 intervals are consecutive, return “true”. If not, return “false”.
(3) Fetch the difference between 2 intervals in an interval, using Interval#gap method.
In this example, the interval between “2013-08-16~2013-08-18” is fetched.
When there is no difference between intervals, return null.
(4) Fetch the overlapping interval between 2 intervals, using Interval#overlap method.
In this example, the interval between “2013-08-15~2013-08-16” is fetched.
When there is no overlapping interval, return null.
• Convert the intervals into Period when comparing them from abstract perspectives such as Month or Day.
// Convert to Period
interval1.toPeriod();
Period
For example, when Period of “1 month” is added to Instant(DateTime)indicating “March 1”, DateTime will be
“April 1”.
The result of adding the Period of “1 month” to “March 1” and “April 1” is as shown below.
The result of adding a Period of “1 month” differs depending on the target DateTime.
• Single field Period (Example:Type having values of single unit such as “1 Day” or “1 month”)
• Any field Period (Example:Type indicating the period and having values of multiple units such as “1
month 2 days 4 hours”)
How to set
joda:format tag
joda:format tag is used to format the objects of DateTime, LocalDateTime, LocalDate and LocalTime.
Output result
1.
value Set the instance of ReadableInstant or ReadablePartial.
2.
var Variable name
3.
scope Scope of variables
4.
locale Locale information
5.
style Style information for doing formatting (2 digits. Set the style for date and time. Values that can
be entered are S=Short, M=Medium, L=Long, F=Full, -=None)
6.
pattern Pattern for doing formatting (yyyyMMdd etc.). For patterns that can be entered, refer to Input
and Output.
7.
Time zone
dateTimeZone
For other Joda-Time tags, refer to Joda Time JSP tags User guide.
Note: When the date and time is displayed by specifying style attributes, the displayed contents differ
depending on browser locale. Locale of the format displayed in the above style attribute is “en”.
Using Spring MVC, sample for displaying a month wise calendar, is shown below.
@Controller
@RequestMapping("calendar")
public class CalendarController {
@RequestMapping
public String today(Model model) {
LocalDate today = new LocalDate();
int year = today.getYear();
int month = today.getMonthOfYear();
return month(year, month, model);
}
@RequestMapping(value = "month")
public String month(@RequestParam("year") int year,
@RequestParam("month") int month, Model model) {
LocalDate firstDayOfMonth = new LocalDate(year, month, 1);
LocalDate lastDayOfMonth = firstDayOfMonth.dayOfMonth()
.withMaximumValue();
break;
}
if (weekList == null) {
weekList = new ArrayList<LocalDate>();
calendar.add(weekList);
}
if (d.isBefore(firstDayOfMonth) || d.isAfter(lastDayOfMonth)) {
// skip if the day is not in this month
weekList.add(null);
} else {
weekList.add(d);
}
model.addAttribute("output", output);
return "calendar";
}
}
The CalendarOutput class mentioned below is JavaBean having the consolidated information to be output on
the screen.
// omitted getter/setter
}
Warning: For the sake of simplicity, this sample code includes all the logic in the handler method of
Controller, but in real scenario, this logic should be delegated to Helper classes to improve maintain-
ability.
<p>
<a
href="${pageContext.request.contextPath}/calendar/month?year=${f:h(output.yearOfPrevM
Prev</a> <a
href="${pageContext.request.contextPath}/calendar/month?year=${f:h(output.yearOfNextM
→</a> <br>
<joda:format value="${output.firstDayOfMonth}"
pattern="yyyy-M" />
</p>
<table>
<tr>
<th>Mon.</th>
<th>Tue.</th>
<th>Wed.</th>
<th>Thu.</th>
<th>Fri.</th>
<th>Sat.</th>
<th>Sun.</th>
</tr>
<c:forEach var="week" items="${output.calendar}">
<tr>
<c:forEach var="day" items="${week}">
<td><c:choose>
<c:when test="${day != null}">
<joda:format value="${day}"
pattern="d" />
</c:when>
<c:otherwise> </c:otherwise>
</c:choose></td>
</c:forEach>
</tr>
</c:forEach>
</table>
Access {contextPath}/calendar to display the calendar below (showing result of November 2012).
7.4.3 Appendix
The class called java.time.chrono.JapaneseDate is offered in Java8 for Japanese calendar operation
however it is possible to deal with the Japanese calendar by java.util.Calendar class in older Java
version.
Specifically, it is necessary to specify the following java.util.Locale in the java.util.Calendar
class and java.text.DateFormat class.
Below, it shows an example of the Japanese calendar display using the Calendar class.
df1.format(cal.getTime()); // "H27.06.05"
df2.format(cal.getTime()); // "平成 27/06/05"
cal1.setTime(df1.parse("H27.06.05"));
cal2.setTime(df2.parse("平成 27/06/05"));
Note:
7.5.1 Overview
In application development, testing may need to be carried out at any random date and time and not necessarily at
system time of the server. Even in Production environment, there could be cases wherein recovery is performed
by shifting the date to earlier date.
Therefore, it is desirable to have a setting that can fetch any date and time at development and operation sides
instead of system time of the server.
The common library providing a component (In the following, API is referred as “Date Factory”) for obtaining
the system time.
Component provided by common library is divided into two artifacts terasoluna-gfw-common and terasoluna-
gfw-jodatime,
• terasoluna-gfw-common is a Date Factory that uses only the Java standard API
Following is the class diagram of components that provided by the common library.
terasoluna-gfw-common
Interface Description
Interface to obtain an instance of the following classes as the system time
provided by Java.
org.terasoluna.gfw.common.date.
• java.util.Date
ClassicDateFactory
• java.sql.Timestamp
• java.sql.Date
• java.sql.Time
The common library provides the following classes as an implementation
class of the interface.
• org.terasoluna.gfw.common.date.DefaultClassicDateFacto
terasoluna-gfw-jodatime
Interface Description
Interface to obtain an instance of the following classes as the system time
provided by Joda Time.
org.terasoluna.gfw.common.date.jodatime. • org.joda.time.DateTime
JodaTimeDateTimeFactory
The implementation class of Date Factory interface is defined in the bean definition file and an instance of the
Date Factory is used by injection in Java class.
Depending on the intended use, select from the following implementation classes.
Note: It is recommended to define the bean definition file that sets implementation class in [projectName]-
env.xml so that it can be changed according to the environment. Using Date Factory, the date and time can be
changed just by changing the settings of bean definition file, without having to change the source code. Example
of Bean definition file is described later.
Tip: If you want to test in JUnit by changing the date and time, it is also possible to set a random time by
replacing the implementation class of the interface to mock class. For replacement method refer [Unit Test].
pom.xml setting
<dependencies>
</dependencies>
Tip: The configuration method if do not want to use the terasoluna-gfw-parent as a Parent project
If the terasoluna-gfw-parent project is not specified as a parent project, version specification should be done
separately.
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-jodatime</artifactId>
<version>5.2.0.RELEASE</version>
<type>pom</type>
</dependency>
In the above example 5.2.0.RELEASE is specified but it should be actual version which is specified at project side.
Use org.terasoluna.gfw.common.date.jodatime.DefaultJodaTimeDateFactory.
Java class
@Inject
JodaTimeDateFactory dateFactory; // (2)
// omitted
}
(3) Call the method that returns the class instance of the date to be used
In above example org.joda.time.DateTime type instance is fetched.
Use org.terasoluna.gfw.common.date.jodatime.JdbcFixedJodaTimeDateFactory.
(1)
(3)
Java class
@Inject
JodaTimeDateFactory dateFactory;
return "date/dateTimeDisplay";
}
Execution result
SQL log
Access log is output to DB if Date Factory is called. In order to output SQL log, Log4jdbcProxyDataSource
described in Database Access (Common) is used.
Returning time obtained by adding the difference registered in DB to the server system time
Use org.terasoluna.gfw.common.date.jodatime.JdbcAdjustedJodaTimeDateFactory.
(1)
In this example, the difference is in “minutes”. (DB data is specified as -1440 minutes = previous day)
By converting the retrieved result into milliseconds (integer value), the unit for DB value can be set to any one of
the units namely, hours, minutes, seconds or milliseconds.
Note: Above SQL is for PostgreSQL. For Oracle, it is better to use NUMBER(19) instead of BIGINT.
Tip: If you want to make difference value unit other than the “minutes”, the following SQL can be specified in
the adjustedValueQuery property.
Java class
@Inject
JodaTimeDateFactory dateFactory;
return "date/dateTimeDisplay";
}
(4)
Pass the system time retrieved from Date Factory to the screen.
When confirming the results, the output is the time that is 1440 minutes subtracted from the execution
(5)
time.
Pass the time that retrieved after calling the Date Factory method to the screen for confirmation.
(6)
Execution result
SQL log
When the difference value is set to 0 and used in production environment, performance deteriorates as the differ-
ence is fetched each time from DB. Therefore, in JdbcAdjustedJodaTimeDateFactory, it is possible to cache the
difference values obtained by the SQL. Once the value fetched at booting is cached, table is not accessed for each
request.
(1) When it is true, the difference value fetched from table is cached. By default it is false so the value is
not cached.
When it is false, SQL is executed each time when the method of Date Factory is called.
When the difference value is to be changed after setting cache, cache value can be reloaded by executing JdbcAd-
justedJodaTimeDateFactory.reload() method after changing the table value.
Java class
@Controller
@RequestMapping(value = "reload")
public class ReloadAdjustedValueController {
@Inject
JdbcAdjustedJodaTimeDateFactory dateFactory;
// omitted
@RequestMapping(method = RequestMethod.GET)
public String reload() {
// omitted
}
}
7.5.3 Testing
When carrying out testing, it may be necessary to change to another date and time instead of the current date and
time.
Unit Test
In Unit Test, sometimes it needs to be verified whether the time is registered and the registered time has been
updated as expected.
In such cases, if the server time is registered as it is during the process, it becomes difficult to perform regression
test in JUnit, as the value differs with each test execution. Here, by using Date Factory, the time to be registered
can be fixed to any value.
Use mock to match the time in milliseconds. An example wherein fixed date is returned by setting a value in Date
Factory, is shown below. In this example, mockito is used for mock.
Java class
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
// omitted
@Inject
StaffRepository staffRepository;
@Inject
JodaTimeDateFactory dateFactory;
@Override
public Staff staffUpdateTel(String staffId, String tel) {
// ex staffId=0001
Staff staff = staffRepository.findOne(staffId);
// ex tel = "0123456789"
staff.setTel(tel);
// set ChangeMillis
staff.setChangeMillis(dateFactory.newDateTime()); // (1)
staffRepository.save(staff);
return staff;
}
// omitted
JUnit source
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
StaffService service;
StaffRepository repository;
JodaTimeDateFactory dateFactory;
DateTime now;
@Before
public void setUp() {
service = new StaffService();
dateFactory = mock(JodaTimeDateFactory.class);
repository = mock(StaffRepository.class);
now = new DateTime();
service.dateFactory = dateFactory;
service.staffRepository = repository;
when(dateFactory.newDateTime()).thenReturn(now); // (2)
}
@After
public void tearDown() throws Exception {
}
@Test
public void testStaffUpdateTel() {
// execute
Staff staff = service.staffUpdateTel("0001", "0123456789");
//assert
assertThat(staff.getChangeMillis(), is(now)); // (3)
}
}
(2) Set the date and time to the return value of DateFactory in mock.
(3) success is returned since it is same as the fixed value that has been set.
The example below illustrates a Service class which is implemented with the specification of “Reserved tour
cannot be cancelled if the cancellation is sought less than 7 days before the departure day”.
Java class
import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory;
// omitted
@Inject
JodaTimeDateFactory dateFactory;
// omitted
@Override
public void cancel(String reserveNo) throws BusinessException {
// omitted
if (today.isAfter(cancelLimit)) { // (3)
// omitted (4)
}
// omitted
}
(1) Fetch current date and time. For LocalDate, refer to Date Operations (Joda Time).
(2) Calculate the last date up to which the tour can be cancelled.
(3) Check if today’s date is later than the last date for cancellation.
(4) BusinessException is thrown if the date exceeds the last date for cancellation.
JUnit source
@Before
public void setUp() {
service = new ReserveServiceImpl();
// omitted
@Test
public void testCancel01() {
// omitted
// run
service.cancel(reserveNo); // (7)
// omitted
}
@Test(expected = BusinessException.class)
public void testCancel02() {
// omitted
try {
// run
service.cancel(reserveNo); // (9)
fail("Illegal Route");
} catch (BusinessException e) {
// assert message if required
throw e;
}
}
(5) Set the departure date to 2012/10/10 in the tour reservation information to be fetched from Repository
class.
(7) Execute Cancel. Cancellation is successful as the date is prior to the last date for cancellation.
(9) Execute Cancel. Cancellation fails as the date falls after the last date for cancellation.
Integration Test
In Integration Test, there may be cases wherein data of several days (for example: files) is created and transferred
in a single day, for communicating with the system.
When the actual date is 2012/10/1, Use JdbcAdjustedJodaTimeDateFactory and set the SQL to calculate the
difference with test execution date.
1 Set the difference between 9:00-11:00 as “0 days” and return value of Date Factory as 2012/10/1.
2 Set the difference between 11:00-13:00 as “0 days” and return value of Date Factory as 2012/10/10.
3 Set the difference between 13:00-15:00 as “30 days” and return value of Date Factory as 2012/10/31.
4 Set the difference between 15:00-17:00 as “31 days” and return value of Date Factory as 2012/11/1.
System Test
In System Test, testing may be carried out by creating test scenarios assuming the operation date.
Use JdbcAdjustedJodaTimeDateFactory and set SQL that calculates the date difference. Create a mapping table
for actual date and operation date like 1, 2, 3 and 4 as shown in the figure. Testing can be carried out on the desired
date, only by changing the difference value in the table.
Production
By setting the difference value to ‘0’ using JdbcAdjustedJodaTimeDateFactory, the return value of Date Factory
can be set to the date same as the actual date without changing the source. Even the bean definition file need not
be changed from System Test onwards. Further, even if the need to change date and time arises, return value of
Date Factory can be changed by changing the table value.
Warning: When using in Production environment, verify that the difference value in the table used in Pro-
duction environment is 0.
Configuration example
•窶「When using the table for the first time in Production environment,
– INSERT INTO operation_date (diff) VALUES (0);
•窶「When test execution is completed in Production environment
– UPDATE operation_date SET diff=0;
Always, useCache should be set to ‘true’.
When there is no change in time, it is recommended to change the configuration file to DefaultJodaTimeDateFac-
tory.
7.6.1 Overview
There are very few operations in string standard API of Java which specialise in Japanese language.
A process must be devised independently for conversion of full width katakana / half width katakana and
determination of a string consisting of only half width katakana.
Also, note that although in Java, all the strings are represented in Unicode
the special characters like 吉 are represented in unicode by char type 2 (32 bits) which is referred to as surrogate
pair.
Even while handling these characters, an implementation which takes into account various types of characters are
necessary to prevent occurrence of unexpected behavior.
Trim
String#trim method can be used as well while carrying out half width blank trim operation, however, while
carrying out complex trim operations like only leading and trailing trim operation, trim operation of any string
etc. org.springframework.util.StringUtils provided by Spring should be preferably used.
Note: There is no change in the behaviour even if surrogate pair string is specified in the first argument of
StringUtils#trimLeadingCharacter and StringUtils#trimTrailingCharacter. Also note
that, since the second argument is of char type, surrogate pair cannot be specified.
Padding, Suppress
While carrying out padding (string padding) and suppress (string takeout) operations, a method
provided by String class can be used.
int num = 1;
Warning: If a surrogate pair is included while carrying out padding of apparent length, appropriate results
cannot be obtained since surrogate pair cannot be taken into account by String#format. In order to achieve
the padding by using surrogate pair, it is necessary to count number of characters considered as surrogate pair
described later, calculate appropriate number of characters that should be padded and join the strings.
When the length of the string considered as surrogate pair is to be fetched, it is not possible to
simply use String#length method.
Since surrogate pair is represented by 32 bits (char type 2), the count tends to exceed than the apparent number of
characters.
A method which returns length of the string after considering combining characters and surrogate pair is given
below.
return length;
}
When a string of specified range is to be fetched, unintended results may be obtained if only
String#substring is used.
In the example above, when you try to fetch “吉田” by taking out 2 characters from 0th character (beginning),
only “吉” could be fetched since the surrogate pair is represented by 32 bits (char type 2).
In such a case, String#substring method must be used by searching start and end positions considering the
surrogate pair, by using String#offsetByCodePoints.
An example wherein 2 characters are taken from the beginning (surname part) is shown below.
String split
Note:
Note:
Please note that behaviour while passing a blank character in String#split changes in Java SE 7
environment, and Java SE 8. Refer Pattern#split Javadoc
// Java SE 7 => {, A, B, C}
// Java SE 8 => {A, B, C}
Full width and half width character conversion is carried out by using API of
org.terasoluna.gfw.common.fullhalf.FullHalfConverter class provided by common
library.
FullHalfConverter class adopts a style wherein pair definition of full width and half width characters
for conversion (org.terasoluna.gfw.common.fullhalf.FullHalfPair) is registered in advance.
FullHalfConverter object for which default pair definition is registered, is provided as a INSTANCE con-
stant of org.terasoluna.gfw.common.fullhalf.DefaultFullHalf class in the common library.
For default pair definition, refer DefaultFullHalf source .
Note: When change requirements are not met in the default pair definition provided by common library,
FullHalfConverter object registering a unique pair definition should be created. For basic methods for
creation, refer Creating FullHalfConverter class for which a unique full width and half width character pair
definition is registered .
It is necessary to add following common library as dependency library in case of Full width, half width string
conversion is used.
<dependencies>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-string</artifactId>
</dependency>
</dependencies>
toFullwidth method of FullHalfConverter is used while converting a half width character to full width
character.
(1) Pass the string which contains half width characters to argument of toFullwidth method and
convert to full width string.
In this example, it is converted to "ア゛!A8ガザ". Also, note that the characters for which a pair is
not defined ("ザ" in this example) are returned without any change.
toHalfwidth method of FullHalfConverter is used while converting a full width character to half width
character.
(1) Pass the string which contains full width characters to argument of toHalfwidth method and
convert to half width characters.
In this example, it is converted to "A!アガサ". Also, note that the characters for which a pair is not
defined ("サ" of this example) are returned without any change.
Note: FullHalfConverter cannot convert combining characters that represent a single character using 2 or
more characters (Example: “"シ"(\u30b7) + voiced sound mark(\u3099)”)to half width character (Exam-
ple: "ジ"). When combining characters are to be converted to half width characters, FullHalfConverter
must be used after converting the same to integrated characters (Example:"ジ"(\u30b8))by carrying out text
normalization.
java.text.Normalizer is used while carrying out text normalization. Note that, when combining characters
are to be converted to integrated characters, NFC or NFKC is used as a normalization format.
Implementation example wherein NFD (analyse by canonical equivalence) is used as a normalization format
Implementation example wherein NFC (analyse by canonical equivalence, and integrate again) is used as a nor-
malization format
Implementation example wherein NFKD (analyse by compatibility equivalent) is used as a normalization format
Implementation example wherein NFKC (analyse by compatibility equivalent and integrate again) is used as a
normalization format
Creating FullHalfConverter class for which a unique full width and half width character pair definition is
registered
FullHalfConverter for which a unique full width and half width character pair definition is registered can
also be used without using DefaultFullHalf.
How to use FullHalfConverter for which a unique full width character and half width character pair
definition is registered, is shown below.
Implementation example of a class that provides FullHalfConverter for which a unique pair definition is
registered
static {
// (1)
FullHalfPairsBuilder builder = new FullHalfPairsBuilder();
// (2)
builder.pair("ー", "-");
// (3)
for (char c = '!'; c <= '~'; c++) {
String fullwidth = String.valueOf((char) (c + FULL_HALF_CODE_DIFF));
builder.pair(fullwidth, String.valueOf(c));
}
// (4)
builder.pair("。", "。").pair("「", "「").pair("」", "」").pair("、", "、")
.pair("・", "・").pair("ァ", "ァ").pair("ィ", "ィ").pair("ゥ", "ゥ")
.pair("ェ", "ェ").pair("ォ", "ォ").pair("ャ", "ャ").pair("ュ", "ュ")
.pair("ョ", "ョ").pair("ッ", "ッ").pair("ア", "ア").pair("イ", "イ")
.pair("ウ", "ウ").pair("エ", "エ").pair("オ", "オ").pair("カ", "カ")
.pair("キ", "キ").pair("ク", "ク").pair("ケ", "ケ").pair("コ", "コ")
.pair("サ", "サ").pair("シ", "シ").pair("ス", "ス").pair("セ", "セ")
.pair("ソ", "ソ").pair("タ", "タ").pair("チ", "チ").pair("ツ", "ツ")
.pair("テ", "テ").pair("ト", "ト").pair("ナ", "ナ").pair("ニ", "ニ")
.pair("ヌ", "ヌ").pair("ネ", "ネ").pair("ノ", "ノ").pair("ハ", "ハ")
.pair("ヒ", "ヒ").pair("フ", "フ").pair("ヘ", "ヘ").pair("ホ", "ホ")
.pair("マ", "マ").pair("ミ", "ミ").pair("ム", "ム").pair("メ", "メ")
.pair("モ", "モ").pair("ヤ", "ヤ").pair("ユ", "ユ").pair("ヨ", "ヨ")
.pair("ラ", "ラ").pair("リ", "リ").pair("ル", "ル").pair("レ", "レ")
.pair("ロ", "ロ").pair("ワ", "ワ").pair("ヲ", "ヲ").pair("ン", "ン")
.pair("ガ", "ガ").pair("ギ", "ギ").pair("グ", "グ")
.pair("ゲ", "ゲ").pair("ゴ", "ゴ").pair("ザ", "ザ")
.pair("ジ", "ジ").pair("ズ", "ズ").pair("ゼ", "ゼ")
.pair("ゾ", "ゾ").pair("ダ", "ダ").pair("ヂ", "ヂ")
.pair("ヅ", "ヅ").pair("デ", "デ").pair("ド", "ド")
.pair("バ", "バ").pair("ビ", "ビ").pair("ブ", "ブ")
.pair("べ", "ベ").pair("ボ", "ボ").pair("パ", "パ")
.pair("ピ", "ピ").pair("プ", "プ").pair("ペ", "ペ")
.pair("ポ", "ポ").pair("ヴ", "ヴ").pair("\u30f7", "ヷ")
.pair("\u30fa", "ヺ").pair("゛", "゙").pair("゜", "゚").pair(" ", " ");
// (5)
INSTANCE = new FullHalfConverter(builder.build());
}
}
(2) Half width character corresponding to "ー"of full width character set to "ー"(\uFF70) in
DefaultFullHalf is changed to "-"(\u002D) in this example.
Further, although "-"(\u002D) is also included in the process target given below (3), the pair
definition defined earlier is given the precedence.
(3) In this example, a pair is defined for code values of full width of unicode from "!" to "∼" and of
half width of unicode from "!" to "~" using a loop process which use the characteristic “code value
sequence is same”.
(4) Since code value sequence for the characters other than given in (3) does not match for full width
characters and half width characters, define a pair individually for respective characters.
Note: For the values that can be specified in the argument of FullHalfPairsBuilder#pair method, refer
FullHalfPair constructor JavaDoc
(1) Use toHalfwidth method of FullHalfConverter object for which a unique pair definition is
registered and convert the string containing full width characters to half width string.
In this example, it is converted to "ハロ-ワ-ルド!". ("-" is \u002D)
A code point set function provided by common library should be used for checking character type.
Here, how to implement a character type check by using a code point set function is explained.
It is necessary to add Code point set class provided by common library as dependency library in case of Code
point set check (character type check) is used.
A method wherein an instance is created from a code point set class ( Class<? extends CodePoints>)
(1) Pass code point set class in CodePoints#of method (factory method) and fetch an instance.
In this example, an instance of code point set class
(org.terasoluna.gfw.common.codepoints.catalog.ASCIIPrintableChars) of
Ascii printable characters is fetched.
Note: Code point set class exists multiple times in the module, same as CodePoints class. Although other
modules which provide code point set also exist, these modules must be added to their own projects when required.
For details, refer Code point set class provided by common library.
Further, a new code point set class can also be created. For details, refer Creating new code point set class.
(1) Call constructor by using new operator and generate an instance of code point set class.
In this example, an instance of code point set class ( ASCIIPrintableChars) of Ascii printable
characters is generated.
(1) Add code point of int to Set and generate an instance by passing the Set in constructor of
CodePoints.
In this example, an instance of code point set for characters "a" and "b" is generated.
• When code point set string is to be passed by using variable length argument
(1) Generate an instance by passing code point set string in constructor of CodePoints.
In this example, an instance of code point set for characters "a" and "b" is generated.
(2) Code point set string can also be passed by dividing it in the arguments. Result is same as (1).
A new code point set instance can be created by performing set operation for code point set.
Further, note that status of source code point set does not change due to set operation.
A method wherein an instance of code point set is created by using set operation is given below.
When an instance of code point set is created by using union set method
(1) Calculate union of two code point sets by using CodePoints#union method and create an
instance of new code point set.
In this example, union of “code point set included in string"ab"” and “code point set included in
string "cd"” is calculated and an instance of new code point set (code point set included in string
"abcd") is generated.
When an instance of code point set is created by using difference set method
(1) Calculate difference set of two code point sets by using CodePoints#subtract method and
create an instance of new code point set.
In this example, difference set of “code point set included in string "abcd"” and “code point set
included in string "cd"” is calculated and an instance of new code point set (code point set included
in string "ab") is created.
(1) Calculate intersection set of two code point sets by using CodePoints#intersect method and
create an instance of new code point set.
In this example, calculate intersection set of “code point set included in string "abcd"” and “code
point set included in string "cde"” is calculated and an instance of new code point set (code point set
included in string "cd")is created.
How to use a method which is used while checking the string is given below.
containsAll method
Determine whether the entire string for checking is included in the code point set.
boolean result;
result = jisX208KanaCp.containsAll("カ"); // true
result = jisX208KanaCp.containsAll("カナ"); // true
result = jisX208KanaCp.containsAll("カナ a"); // false
firstExcludedContPoint method
Return the first code point which is not included in the code point set, from the string targeted for checking.
Further, return CodePoints#NOT_FOUND when the entire string for checking is included in code point set.
int result;
result = jisX208KanaCp.firstExcludedCodePoint("カナ a"); // 0x0061 (a)
result = jisX208KanaCp.firstExcludedCodePoint("カ a ナ"); // 0x0061 (a)
result = jisX208KanaCp.firstExcludedCodePoint("カナ"); // CodePoints#NOT_FOUND
allExcludedCodePoints method
Return Set of the code point which is not included in the code point set, from the string targeted for checking.
Set<Integer> result;
result = jisX208KanaCp.allExcludedCodePoints("カナ a"); // [0x0061 (a)]
result = jisX208KanaCp.allExcludedCodePoints("カ a ナ b"); // [0x0061 (a), 0x0062 (b)]
result = jisX208KanaCp.allExcludedCodePoints("カナ"); // []
It can be checked whether the entire string targeted for checking is included in the specified code point set by
specifying code point set class in @org.terasoluna.gfw.common.codepoints.ConsistOf
annotation.
How to use is shown below.
When there is only one code point set used for checking
@ConsisOf(JIS_X_0208_Hiragana.class) // (1)
private String firstName;
(1) Check whether the string specified in the targeted field is entirely “Hiragana of JIS X 0208”.
When there are multiple code point sets used for checking
(1) Check whether the string specified in the targeted field is entirely “Hiragana of JIS X 0208” or
“Katakana of JIS X 0208”.
Note: If string of length N is checked by code point sets M, a checking process that contains N x M is employed.
When the string is large, it is likely to cause performance degradation. Hence, a new code point set class that acts
as a union set of code point set used for checking is created, that class alone should be specified.
When a new code point set class is to be created, specify code point using constructor by inheriting
CodePoints class.
A method wherein a new code point set class is created is given below.
When a new code point set class is created by specifying code point
When a new code point set class is created by using a set operation method of code point set class
How to create a code point set using a union set consisting of “Hiragana” and “Katakana”
How to create a code point set using difference set consisting of “half width katakana excluding symbols (。「」、・)”
super(new JIS_X_0201_Katakana().subtract(symbolCp));
}
}
Note: When the code point set class used in set operation (X_JIS_0208_Hiragana or
X_JIS_0208_Katakanaetc in this example) is not to be used individually, it must be ensured that code
point is not needlessly cached, by using new operator and calling constructor. If it is cached by using
CodePoints#ofmethod, code point set used only during set operation calculation remains in the heap resulting
in load on the memory. On the other hand, if it is used individually, it should be cached using CodePoints#of
method.
<dependency>
(1) Ascii control characters set. <groupId>org.terasoluna.gfw</gr
ASCIIControlChars
(0x0000-0x001F、0x007F) <artifactId>terasoluna-gfw-code
</dependency>
<dependency>
(4) JIS X 0201 katakana set. <groupId>org.terasoluna.gfw.cod
JIS_X_0201_Katakana
Symbols (。「」、・) included as <artifactId>terasoluna-gfw-code
well. </dependency>
<dependency>
(6) Row 2 of JIS X 0208: Special <groupId>org.terasoluna.gfw.cod
JIS_X_0208_SpecialChars
characters set. <artifactId>terasoluna-gfw-code
</dependency>
(9)
1586 Row 5 of JIS Xfunctions
7 General-purpose 0208: Katakana (Same
that do not as above)
depend on the application
JIS_X_0208_Katakana
set.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: JIS_X_0208_SpecialCharscodepoint set class is a special character set corresponding to JIS chinese
characters (JIS X 0208)-section 01-02. Double byte dash (-) of JIS chinese characters is EM DASH and the corre-
sponding UCS(ISO/IEC 10646-1, JIS X 0221, Unicode) codepoints usually correspond to U+2014. However, in
the conversion table offered by Unicode consortium , characters supported by Unicode are HORINZONTAL BAR
(U+2015) instead of EM DASH.. Since general conversion rules that are being used and Unicode conversion table
vary, problems may occur during actual use if codepoint set is defined as per Unicode conversion table. Therefore,
codepoint set is defined in 、JIS_X_0208_SpecialChars codepoint set class by converting HORINZONTAL
BAR (U+2015) to EM DASH (U+2014).
7.7.1 Overview
Bean mapping is a process to copy the field values of one Bean to another Bean.
There are many cases where bean mapping is necessary while passing the data between different layers of the
application (application layer and domain layer).
For example, AccountForm object of application layer is converted to Account object of domain layer.
Since the domain layer should not depend on the application layer, AccountForm object cannot be used as it is in
the domain layer.
Hence, AccountForm object is Bean mapped to Account object and Account object is used in the domain
layer.
Thus, linear dependency relation can be maintained between application layer and domain layer.
These objects can be mapped by calling getter/setter of Bean and passing the data.
However, since a complex process results in deterioration of the program readability, in this guideline, it is
recommended to use Dozer a Bean mapping library available in OSS.
By using Dozer, different types of copies for copy source class and copy destination class and copy of nested
output.setUserId(user.getUserId());
output.setFirstName(user.getFirstName());
output.setLastName(user.getLastName());
output.setTitle(user.getTitle());
output.setBirthDay(user.getBirthDay());
output.setGender(user.getGender());
output.setStatus(user.getStatus());
Warning: Classes added by JSR-310 Date and Time API cannot be used in default state due to
generation of exceptions, and hence a custom converter must be created.
Dozer is a mapping function library of JavaBean. Values are copied recursively (nested structure) from conversion
source Bean to conversion destination Bean.
<bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
<property name="mappingFiles"
value="classpath*:/META-INF/dozer/**/*-mapping.xml" /><!-- (1) -->
</bean>
@Inject
Mapper beanMapper;
Mapping when the field name and the type between Beans is same
Dozer can perform mapping by default without creating mapping definition XML file if the field name between
the Beans is same.
Perform Bean mapping using map method of Mapper as given below. After executing the method given below,
a new Destination object is created and each field value of source is copied to the created Destination object.
(1) Pass the object to be copied as the first argument and the Bean class where it is to be copied as the
second argument.
The output of the above code is as given below. The value of object to be copied is set in the created object.
1
SourceName
To copy the field of source object to already existing destination object, perform the following
System.out.println(destination.getId());
System.out.println(destination.getName());
(1) Pass the object to be copied as the first argument and the object where it is to be copied as the second
argument.
The output of the above code is as given below. The value of object to be copied is reflected in the destination
where it is to be copied.
1
SourceName
Note: The value that does not exist in Source class does not change before and after copying to the field of
Destination class.
Example of Mapping
The output of the above code is as given below. Since there is no title field in the Source class, the value of
title field of Destination object remains the same as the field value before copying.
1
SourceName
DestinationTitle
Mapping when the field name is the same and type between Beans is different
When field type of Bean is different in copy source and copy destination, mapping of the type where type conver-
sion is supported can be done automatically.
The conversion given below is an example where it is possible to convert without a mapping definition XML file.
Example of Mapping
The output of the above code is as given below. The value can be copied even when the type is different.
123.45
When field name of copy source is different from the copy destination, creating mapping definition XML file and
defining the field for Bean mapping enables conversion.
To define Bean definition for using Dozer, create mapping definition XML file called (any value)-mapping.xml in
the src/main/resources/META-INF/dozer folder.
<mapping>
<class-a>com.xx.xx.Source</class-a><!-- (1) -->
<class-b>com.xx.xx.Destination</class-b><!-- (2) -->
<field>
<a>id</a><!-- (3) -->
<b>destinationId</b><!-- (4) -->
</field>
<field>
<a>name</a>
<b>destinationName</b>
</field>
</mapping>
</mappings>
(1) Specify fully qualified class name (FQCN) of copy source Bean in <class-a> tag.
(2) Specify fully qualified class name (FQCN) of copy destination Bean in <class-b> tag.
(3) Specify field name for mapping of copy source Bean in <a> tag of <field> tag.
(4) Specify field name for mapping of copy destination Bean corresponding to (3) in <b> tag of
<field> tag.
Example of Mapping
(1) Pass object to be copied as the first argument and the Bean class where it is to be copied as the second
argument. (no difference with basic mapping.)
1
SourceName
Mapping definition XML file existing under META-INF/dozer of class path is read in mappingFiles property
in accordance with the setting of Bean definition for using Dozer. File name should be (any value)-mapping.xml.
The settings are applied if mapping between Source class and Destination class is defined in any file.
Note: It is recommended to create a mapping definition XML file in each Controller and name the file as -
mapping.xml (value obtained by removing Controller from Controller name). For example, mapping definition
XML file of TodoController is created in src/main/resources/META-INF/dozer/todo-mapping.xml.
One-way/Two-way mapping
The mapping defined in mapping XML is two-way mapping by default. In the above example, mapping is done
from Source object to Destination object however, mapping can also be done from Destination object
to Source object.
To specify only one-way mapping, set "one-way" in the type attribute of <mapping> tag in the mapping
field definition.
Example of Mapping
1
SourceName
If one-way is specified, an error does not occur even if the mapping is done in the reverse direction. The copy
process is ignored. This is because Source field corresponding to Destination field does not exist if mapping
is not defined.
beanMapper.map(destination, source);
System.out.println(source.getId());
System.out.println(source.getName());
1
SourceName
It is possible to map the field of Bean to be copied to the field with Nested attributes of the Bean where it is to be
copied. (In Dozer terminology, it is called Deep Mapping .)
Example: Define as below to map the deptId with EmployeeForm object to deptId of Departmentwith
Employee object.
(1) Specify the field of Employee object for deptId of Employee form.
Example of Mapping
1
John
D01
In the above case, a new instance of Employee, a class of conversion destination is created. The newly created
Department instance is set in the department field of Employee and deptId of the EmployeeForm is
copied.
When the Department object is already set in the department field of Employee, a new instance is not
created but the deptId of EmployeeForm is copied to the deptId field of the existing Department object.
beanMapper.map(source, destination);
System.out.println(department.getDeptId());
System.out.println(destination.getDepartment() == department);
D01
true
Collection mapping
Dozer supports two-way auto-mapping of the following Collection types. When field name is same, mapping
definition XML file is not required.
package com.example.dozer;
public Email() {
}
@Override
public String toString() {
return email;
}
// generated by Eclipse
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((email == null) ? 0 : email.hashCode());
return result;
}
// generated by Eclipse
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Email other = (Email) obj;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
return true;
}
package com.example.dozer;
import java.util.List;
package com.example.dozer;
import java.util.List;
Example of Mapping
emailsSrc.add(new Email("a@example.com"));
emailsSrc.add(new Email("b@example.com"));
emailsSrc.add(new Email("c@example.com"));
accountForm.setEmails(emailsSrc);
System.out.println(account.getEmails());
As shown in the example below, it is necessary to exercise caution when the element is already added to the
Collection field of copy destination Bean.
emailsSrc.add(new Email("a@example.com"));
emailsSrc.add(new Email("b@example.com"));
emailsSrc.add(new Email("c@example.com"));
emailsDest.add(new Email("a@example.com"));
emailsDest.add(new Email("d@example.com"));
emailsDest.add(new Email("e@example.com"));
accountForm.setEmails(emailsSrc);
account.setEmails(emailsDest);
beanMapper.map(accountForm, account);
System.out.println(account.getEmails());
All the elements of Collection of copy source Bean are added to the Collection of copy destination Bean. Two
Email objects with a@exmample.com are “Equal”, but are added easily.
(“Equal” here signifies true when compared by Email.equals and has the same value as
Email.hashCode.)
The above behavior is called as cumulative in Dozer terminology and is a default behavior at the time of Collection
mapping.
The output of the above code is as given below based on this setting.
Note: It is necessary to exercise caution for updating conversion source object with conversion destination object.
In the above example, a@exmample.com of AccountForm is stored in the copy destination.
It can be implemented using the mapping definition XML file settings even to remove the fields existing only in
the copy destination collection.
(1) Set true in the remove-orphans attribute of <field> tag. Default value is false.
The output of the above code is as given below based on this setting.
Only the objects to be copied are reflected in the collection where they are to be copied.
The same result is obtained even with the settings given below.
</mapping>
<!-- omitted -->
</mappings>
(1) Set true in the copy-by-reference attribute of <field> tag. Default value is false.
• non-cumulative
Note: The difference in “non-cumulative and remove-orphans=true” pattern and “copy-by-reference” pattern is
whether the container of Collection after Bean conversion is copy destination or copy source.
In case of “non-cumulative and remove-orphans=true” pattern, the container of Collection after Bean conversion
is copy destination whereas in case of “copy-by-reference” , the container is copy source. It is explained in the
• copy-by-reference
It is necessary to exercise caution when the copy destination has one-to many and many-to-many relation-
ship in the entity of JPA (Hibernate). An unexpected issue may occur when copy destination entity is under
EntityManager control. For example, an SQL of DELETE ALL + INSERT ALL may be issued when the container
of collection is changed and an SQL to UPDATE (DELETE or INSERT when the number of elements differ) the
changes may be issued when copied with “non-cumulative and remove-orphans=true”. Any pattern can be used
depending on the requirements.
Warning: When the Bean to be mapped has String collection, a bug wherein the behavior is not as expected
is generated.
stringsSrc.add("a");
stringsSrc.add("b");
stringsSrc.add("c");
stringsDest.add("a");
stringsDest.add("d");
stringsDest.add("e");
src.setStrings(stringsSrc);
dest.setStrings(stringsDest);
beanMapper.map(src, dest);
System.out.println(dest.getStrings());
If code given above is executed in the non-cumulative and remove-orphans=true settings, following is expected
to be output.
[a, b, c]
[b, c]
[a, b, c]
Tip: In Dozer, the mapping can be performed even between the lists which do not use Generics. At this time,
data type of the object included in conversion source and conversion destination can be specified as HINT. Refer
to Dozer Official Manual -Collection and Array Mapping(Using Hints for Collection Mapping)- for details.
Todo
It is checked that the mapping between Beans that uses Collection<T> fails.
Example :
Execution result :
Mapping of the data type not supported by Dozer can be performed through a custom converter.
• Global Configuration
• Class level
• Field level
Global Configuration is recommended to perform conversion using the same logic in the entire application.
package com.example.yourproject.common.bean.converter;
import org.dozer.DozerConverter;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.util.StringUtils;
@Override
public DateTime convertTo(String source, DateTime destination) {// (3)
if (!StringUtils.hasLength(source)) {
return null;
}
DateTimeFormatter formatter = DateTimeFormat
.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime dt = formatter.parseDateTime(source);
return dt;
}
@Override
public String convertFrom(DateTime source, String destination) {// (4)
if (source == null) {
return null;
}
return source.toString("yyyy-MM-dd HH:mm:ss");
}
(3) Describe the logic to convert from String to DateTime. In this example, Locale is used by default.
(4) Describe the logic to convert from DateTime to String. In this example, Locale is used by default.
dozer-configration-mapping.xml
<configuration>
<custom-converters><!-- (1) -->
(2) Define converter for performing individual conversion. Specify fully qualified class name (FQCN) of
implementation class in the converter type.
Example :
Mapping (two-way)
assertThat(source.getId(), is(1));
assertThat(source.getDate(),is("2012-08-10 23:12:12"));
Refer to Dozer Official Manual -Custom Converters- for the details of custom converter.
Note: The conversion from String to the standard date/time object such as java.utl.Date is explained in
“Mapping from string to date/time object”.
7.7.4 Appendix
The options that can be specified in the mapping definition XML file are explained.
All options can be confirmed in Dozer Official Manual -Custom Mappings Via Dozer XML Files-.
The fields that are not to be copied can be excluded at the time of Bean conversion.
// omitted setter/getter
}
Define as follows to exclude any field of copy source Bean from mapping.
Carry out the settings of field exclusion in the mapping definition XML file as given below.
(1) Set the field you want to exclude in the <field-exclude> element. In this example, if map method is
executed after specification, the title value of destination is not overwritten while copying Destination
object from Source object.
System.out.println(destination.getName());
System.out.println(destination.getTitle());
1
SourceName
DestinationTitle
The title value of destination after mapping is same as in the previous state.
The mapping indicated in Field exclusion settings (field-exclude) is applied while performing Bean conversion in
the entire application. To specify the applicable range of mapping, define map-id as given below.
When the above settings are carried out, title can be excluded from copying by passing map-id (mapidTitleField-
Exclude) to map method. When map-id is not specified, all the fields are copied without applying the settings.
(2) Pass map-id as the third argument and apply the specific mapping rules.
1
SourceName
SourceTitle
1
SourceName
DestinationTitle
Tip: map-id can be specified not only by mapping items but also by defining the field. Refer to Dozer Official
Manual -Context Based Mapping- for details.
Note: The same form object can be used for both creating new/updating the existing Web applications. Form
object is copied (mapped) to domain object, however a field may exist where the object is not to be copied
depending on the operations. In this case, use <field-exclude>.
• Example: userId is included in the New form whereas it is not included in the Update form.
In this case, null is set in the userId at the time of update if the same form object is used. If form object is copied
as it is by fetching copy destination object from DB, userId of copy destination becomes null. In order to avoid
this, provide map-id for update and carry out the settings to exclude field of userId at the time of update.
null or empty field of copy source Bean can be excluded from mapping. Set in the mapping definition XML file
as given below.
(1) To exclude the null field of copy source Bean from mapping, set false in the map-null
attribute. Default value is true.
To exclude the empty field of copy source Bean from mapping, set false in the
map-empty-string attribute. Default value is true.
// omitted setter/getter
}
Mapping example
1
DestinationName
DestinationTitle
name and title field of copy source Bean are null or empty, and are excluded from mapping.
The copy source string field can be mapped to the copy destination date/time field.
Date/time
Date only
Time only
Mapping
There are many cases where it is preferable to set a common date format in the project rather than setting an
individual date for each mapping definition.
In such a case, it is recommended to set in the Global configuration file of Dozer.
The date format set in the mapping of the entire application is applied.
Refer to the Dozer Official Manual -Global Configuration- for the details of items that can be configured.
Mapping error
• map-id existing in map method is passed however, the source/target type passed to map process differs
from the definition specified in map-id.
• When conversion is not supported by Dozer and custom converter for conversion does not exist as well.
Since these are normal program bugs, the sections called by map method should be modified appropriately.
Messaging
8.1.1 Overview
In this guideline, it is assumed that a component for email coordination offered by API and Spring Framework of
JavaMail is used.
Note: The description covers only the part related to sending an email. The processing method related to sending
an email is not mentioned. (An example is introduced for Processing method.)
Regarding JavaMail
JavaMail offers an API to send and receive emails in Java. Although it is included in Java EE, it can also be
used in Java SE as an add-on package. By using JavaMail, the email function can be easily incorporated in Java
application.
Also, since it is assumed that an email coordination component of Spring Framework is used in this guideline,
the details related to JavaMail API are not covered. Refer JavaMail API Design Specification for JavaMail API
specifications.
A mail session (Session) manages the information required for connecting to a mail server.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• Fetch the mail session managed by Java EE container through JNDI for a typical enterprise application.
• Fetch the mail session defined by resource factory through JNDI in case of Tomcat.
• Fetch the mail session for which a Bean is defined, from DI container using a static factory method.
• Fetch directly from Java source using static factory method of Session.
Note that, if JavaMailSenderImpl of Spring described later is used, it is possible to connect to a mail server
without handling a mail session directly.
Implementation examples using two methods given below are introduced in this guideline.
Spring Framework offers a component (org.springframework.mail package) for sending an email. The
component included in the package conceals the detail logic related to sending an email and carry out low level
API handling (API calling of JavaMail).
The method by which the component offered by Spring Framework for email transmission sends an email is
explained before the explanation of basic implementation methods.
1622 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(3) Application Create a message (MimeMessage) for sending the email using
MimeMessageHelper method.
(MimeMessagePreparator)
This process is not called while sending the message using *
SimpleMailMessage.
The method to implement a process for sending an email using interface and class below is explained in this
guideline.
•JavaMailSender
•JavaMailSenderImpl
•MimeMessagePreparator
•MimeMessageHelper
•SimpleMailMessage
When a component of Spring Framework for email coordination is used, following libraries must be added.
• JavaMail
1624 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<dependencies>
</dependencies>
Note: In the configuration example above, it is assumed that dependent library version will be managed by parent
project. Hence, <version> element is not specified.
A configuration example while using a mail session offered by application server is given below.
Oracle WebLogic Server 12c Refer Oracle WebLogic Server 12.2.1.0 Documentation.
2.
IBM WebSphere Application Server Refer WebSphere Application Server Version 8.5.5 docu-
3.
Version 8.5 mentation.
Carry out setup for registering a mail session fetched through JNDI, as a Bean.
(1) Specify JNDI name of mail session offered by application server in jndi-name attribute of
<jee:jndi-lookup> element.
When a mail session offered by application server is not used (no authentication)
1626 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Tip: When the connection using TLS is necessary, set true in javaMailProperties property as a key
“mail.smtp.starttls.enable”. Note that, when SMTP server does not support STARTTLS even when
specified as below, plain text is used for communication. When true is set in javaMailProperties property
as a key “mail.smtp.starttls.required” whenever required, an error can occur if it is not possible to
use STARTTLS.
When a plain text email (an email which does not require encode specification or attachments) is to be sent in
English, SimpleMailMessage class offered by Spring is used.
1628 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@Inject
JavaMailSender mailSender; // (1)
@Inject
SimpleMailMessage templateMessage; // (2)
// (3)
SimpleMailMessage message = new SimpleMailMessage(templateMessage);
message.setTo(user.getEmailAddress());
String text = "Hi "
+ user.getUserName()
+ ", welcome to EXAMPLE.COM!\r\n"
+ "If you were not an intended recipient, Please notify the sender.";
message.setText(text);
mailSender.send(message);
// omitted
}
(3) Generate a SimpleMailMessage instance by using Bean of template, specify To header and body
text, and send the message.
Note:
1630 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
2. to Specify To header.
3. cc Specify Cc header.
When multiple addresses are to be specified in To, Cc and Bcc, specify addresses in an array.
Warning: While setting an email header, an email header injection must be considered. Refer Email header
injection countermeasures for details.
When a non-English text email, HTML email and attachments are to be sent,
javax.mail.internet.MimeMessage class is used. In this guideline, a method to create MimeMessage
by using MimeMessageHelper class is recommended.
In this section, the methods to send an email using MimeMessageHelper class are explained below.
An implementation example wherein a text email is sent using MimeMessageHelper class is given below.
@Inject
JavaMailSender mailSender; // (1)
// (2)
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
StandardCharsets.UTF_8.name()); // (3)
helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
helper.setTo(user.getEmailAddress()); // (5)
helper.setSubject("Registration confirmation."); // (6)
String text = "Hi "
+ user.getUserName()
+ ", welcome to EXAMPLE.COM!\r\n"
+ "If you were not an intended recipient, Please notify the sender.";
helper.setText(text); // (7)
}
});
// omitted
}
1632 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: While setting an email header, an email header injection must be considered. Refer Email header
injection countermeasures for details.
Note: While sending an email in Japanese, ISO-2022-JP can also be used in encoding if it is also necessary
to support a mail client which does not support UTF-8. Refer Considerations for ISO-2022-JP encoding for the
points that should be considered while using ISO-2022-JP in encoding.
An implementation example wherein a HTML email is sent using MimeMessageHelper class is shown below.
@Inject
JavaMailSender mailSender; // (1)
// (2)
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
StandardCharsets.UTF_8.name()); // (3)
helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
helper.setTo(user.getEmailAddress()); // (5)
helper.setSubject("Registration confirmation."); // (6)
String text = "<html><body><h3>Hi "
+ user.getUserName()
+ ", welcome to EXAMPLE.COM!</h3>"
+ "If you were not an intended recipient, Please notify the sender.</body></ht
helper.setText(text, true); // (7)
}
});
// omitted
}
1634 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(7) Specify details of body text. Content-Type changes to text/html by specifying true in the second
argument of setText method.
Warning: When a value entered externally is used while generating HTML for email text, countermeasures
for XSS attack should be employed.
An implementation example wherein an email with the attachment is sent using MimeMessageHelper class is
shown below.
@Inject
JavaMailSender mailSender; // (1)
// (2)
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
true, StandardCharsets.UTF_8.name()); // (3)
helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
helper.setTo(user.getEmailAddress()); // (5)
helper.setSubject("Registration confirmation."); // (6)
String text = "Hi "
+ user.getUserName()
+ ", welcome to EXAMPLE.COM!\r\n"
+ "Please find attached the file.\r\n\r\n"
+ "If you were not an intended recipient, Please notify the sender.";
helper.setText(text); // (7)
ClassPathResource file = new ClassPathResource("doc/quickstart.pdf");
helper.addAttachment("QuickStart.pdf", file); // (8)
}
});
// omitted
}
1636 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
An implementation example wherein an email with inline resource is sent using MimeMessageHelper class is
shown below.
@Inject
JavaMailSender mailSender; // (1)
// (2)
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
true, StandardCharsets.UTF_8.name()); // (3)
helper.setFrom("EXAMPLE.COM <info@example.com>"); // (4)
helper.setTo(user.getEmailAddress()); // (5)
helper.setSubject("Registration confirmation."); // (6)
String cid = "identifier1234";
String text = "<html><body><img src='cid:"
+ cid
+ "' /><h3>Hi "
+ user.getUserName()
+ ", welcome to EXAMPLE.COM!\r\n</h3>"
+ "If you were not an intended recipient, Please notify the sender.</body></ht
helper.setText(text, true); // (7)
ClassPathResource res = new ClassPathResource("image/logo.jpg");
helper.addInline(cid, res); // (8)
}
});
// omitted
}
1638 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(7) Specify details of body text. Content-Type changes to text/html by specifying true in the second
argument of setText method.
Note: addInline method should be called after setText method. If done otherwise, a mail client cannot
view the inline resource correctly.
The exception that occurs while sending an email using send method of JavaMailSender is an excep-
tion which inherits org.springframework.mail.MailException. The exception class that inherits
MailException and occurrence conditions of respective exceptions are shown in the table below.
MailParseException
2.
Occurs when an invalid value is set in the properties of
email message.
MailPreparationException
3.
Occurs if an unexpected error occurs while creating an
email message. Unexpected errors, for example, are the
errors that occur in the template library.
Exceptions occurring in MimeMessagePreparator
are wrapped in MailPreparationException and
thrown.
MailSendException
4.
Occurs when an error occurs while sending an email.
Note: Refer Exception Handling for transition to error screen corresponding to specific exceptions.
1640 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
It is not recommended to build an email text directly in Java source as shown in the implementation examples
above for the reasons given below.
• Building the email text in Java source causes poor readability and it may cause an error.
• It becomes necessary to modify, compile and deploy Java source in order to change the email text design.
Hence, it is recommended to use a template library to define an email text design. A template library must
especially be used when the email text is particularly complex.
<dependencies>
</dependencies>
Note: Refer to JavaDoc of FreeMarkerConfigurationFactoryBean for the setup other than men-
tioned above. Also, refer FreeMarker Manual (Programmer’s Guide / The Configuration) for setup
of FreeMarker itself.
<div>
If you were not an intended recipient, Please notify the sender.
</div>
</body>
</html>
</#escape>
Note: Refer FreeMarker Manual (Template Language Reference) for details of template language
1642 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(FIL).
@Inject
JavaMailSender mailSender;
@Inject
Configuration freemarkerConfiguration; // (1)
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
StandardCharsets.UTF_8.name());
helper.setFrom("EXAMPLE.COM <info@example.com>");
helper.setTo(user.getEmailAddress());
helper.setSubject("Registration confirmation.");
Template template = freemarkerConfiguration
.getTemplate("registration-confirmation.ftl"); // (2)
String text = FreeMarkerTemplateUtils
.processTemplateIntoString(template, user); // (3)
helper.setText(text, true);
}
});
// omitted
}
(3) Based on fetched Template, generate a string from the template using
processTemplateIntoString method of
org.springframework.ui.freemarker.FreeMarkerTemplateUtils.
In this example, User object (JavaBeans) consisting of userName property is specified as
a data model. Accordingly, value of userName property is embedded in the location of
${userName} of template file.
8.1.4 Appendix
When sending an email in Japanese, if the email client that receives the sent mail cannot be restricted, it is
necessary to consider use of ISO-2022-JP in encoding. This is because the legacy email client does not support
UTF-8.
When encoding based on character set of JIS X 0208 including ISO-2022-JP is set for the string entered by MS932,
garbling occurs for seven characters described in the table below.
1644 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
This issue occurs during character code conversion through Unicode due to the presence of characters that exist
in MS932 but do not exist in JIS X 0208. In order to avoid garbling, the measures such as replacing character
codes for the garbled characters with alternate characters must be employed. Note that, conversion process is not
necessary while using x-windows-iso2022jp described later.
if (targetStr == null) {
return null;
}
char[] ch = targetStr.toCharArray();
return String.valueOf(ch);
}
Note: Since it is an issue that occurs during mapping in Unicode, conversion is necessary regardless of the
1646 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
character code of input value. Header and body text which may contain strings with Japanese text are used for
conversion. From, To, Cc, Bcc, Reply-To, Subject etc are examples of the header that may contain Japanese text
and are frequently used in general.
Also, when ISO-2-22-JP is set as encoding, extended characters which are not in scope are garbled.
These characters essentially should not be used. If it is necessary to use these characters, settings can be done as
follows as JVM start-up options. Moreover, when ISO-2022-JP encoding is set, these characters can be replaced
so that they can be mapped with x-windows-iso2022jp.
-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP
When extension characters can also be converted to alternate characters, a method wherein conversion is done in
the application independently should also be reviewed similar to seven characters described earlier.
If email header injection attack is successful, an email is sent to an unintended address and thus can end up as
“junk mail”. When a string that has been entered externally is used in the email header (Subject etc) contents, it
becomes necessary to take countermeasures against email header injection attack.
For example, if the following string is set by setSubject method of MimeMessageHelper, the text can be
manipulated by adding a Bcc header.
Following methods can be considered as countermeasures for email header injection attack.
• Set contents to be specified in email header as a fixed value and output entire string that has been entered
externally in the email body text.
• Check that no linefeed character is included in the contents specified in the email header.
Processing method
Since sending an email is a time consuming process, response time can become longer if an email is sent during
a web application request. Hence, an email is usually not sent during a web application request and a method is
adopted wherein an email is sent asynchronously. Although a process to send the email is not described here in
detail, the example is given below which can be used as a reference.
Send an email based on the email information stored in database or message queue
Incorporate the functions given below in the application when you want to send an email based on the email
information stored in database or message queue.
• Register the information of the email to be sent (address, body text, attachment etc) in the database (or
message queue).
• Fetch the email information that has not been sent from database (or message queue) periodically and send
the email through SMTP.
Note that, following points must be taken into consideration during review.
Tip: When an email is sent continuously by a mailing service, it is determined as spam mail. A method can
be considered as a countermeasure wherein the sending sequence is set as random so that emails are not sent
continuously for the same domain.
1648 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
A method wherein GreenMail is used as a fake server to test the email sending function is introduced. GreenMail
can also be used by deploying war file besides using it as a library.
<dependencies>
</dependencies>
Sr.No. Description
@Inject
EmailService emailService;
@Rule
public final GreenMailRule greenMail = new GreenMailRule(
ServerSetupTest.SMTP); // (1)
@Test
public void testSend() {
assertNotNull(messages);
assertEquals(1, messages.length);
// omitted
}
1650 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
8.2.1 Overview
This chapter explains how to send and receive messages which use components for JMS linking of JMS API and
Spring Framework.
What is JMS
JMS is a standard API for using MOM (Message Oriented Middleware) in Java.
JMS architecture exchanges messages from client to client through JMS provider.
Since JMS supports asynchronous messaging, a close coupling can be formed between the clients.
Further, since the message can be stored in Queue by adopting a Point-to-Point model described later, messages
can be received in accordance with the client performance.
On the other hand, since the time lag is likely to occur in the messaging between clients, it is not suitable for the
processes which require a real time response.
For details of JMS, refer Java Message Service (JMS).
While using, the delivery method and the method to send and receive messages described below must be selected
in accordance with the requirements.
• Delivery model
Delivery model consists of two models - Point-to-Point (PTP) and Publisher-Subscriber (Pub/Sub).
A major difference between two models is in whether the sender and recipient ratio is 1:1 or 1:many, and
should be selected based on the use.
PTP model consists of 2 clients wherein one of the clients (Producer) sends a message and the other
client (Consumer) alone receives that message.
Destination of the message in PTP model is called as a Queue.
Producer sends the message to the Queue and the Consumer fetches the message from the Queue, and
the process is carried out.
Message is fetched from the consumer or when the message reaches expiry period, the message is
deleted from the Queue.
Pub/Sub model consists of 2 clients wherein one of the clients (Publisher) issues (Publishes) the
message and delivers that message to multiple other clients (Subscribers).
Destination of the message in Pub/Sub model is called as a Topic.
Subscriber raises a subscription request for the Topic and the Publisher issues a message in Topic.
The message is delivered to all the subscribers who have raised a request for subscription.
This guideline explains how to implement PTP model which is widely used in general.
There are two types of processes for message sending - Synchronous sending method and asynchronous
sending method for sending the messages to Queue or Topic however JMS1.1 supports only synchronous
sending method.
1652 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
There are two types of methods for receiving messages - synchronous receiving method and asynchronous
receiving method while implementing the process for receiving messages in Queue or Topic.
As described later, since use cases of synchronous receiving methods are limited, generally asynchronous
receiving methods are widely used.
When the message is received by Queue or Topic, the processing for the received message is initiated.
Since the processing for one message is initiated without terminating the processing for the other
message, it is suitable for parallel processing.
Receiving the message and related processing is initiated by explicitly calling the function which
receives the message.
The function which receives the message waits till the message is received when the message does
not exist in Queue or Topic.
Hence, the waiting period for the message must be specified by configuring the timeout value.
As an example of synchronous receiving of message, it can be used when the message accumulated in
the Queue, in the Web application is to be fetched and processed at any time like screen operation etc
or when the messages are to be processed periodically in a batch.
Structure Description
Header Control information of message like destination and identifier, extension header
(JMSX) of JMS, JMS provider specific header and application specific header are
stored for JMS provider and application.
Using JMS
When the process is to be implemented by using JMS, it can be executed by using JMS API defined in Java EE
(hereafter referred to as JMS API).
However, this guideline assumes the use of JMS linking component of Spring Framework wherein the merits are
significant as compared to using JMS API as it is (easy description etc).
Hence, details of JMS API are not described.
For details, refer Java API.
Note: For JMS, Java API is standardized however physical protocols of messages are not standardized.
Note: Since JMS implementation is incorporated in Java EE server as a standard, it can be used as a default
(restricted while using JMS provider incorporated in Java EE server), however JMS must be separately
implemented in Java EE server like Apache Tomcat wherein JMS is not incorporated.
1654 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Following are provided as the libraries for sending and receiving messages in Spring Framework.
•spring-jms
•spring-messaging
It offers a component to abstract the infrastructure function which is required for creating application
of messaging base.
It consists of a set of annotations to associate with the message and the method for processing the
same.
By using the components included in the library, implementation style of messaging can be matched.
Although implementation can be done only in spring-jms, implementation method can be combined by using
spring-messaging.
This guideline recommends the use of spring-messaging as well.
Here, prior to explaining basic implementation method, it is explained how the messages are sent and received by
the components for JMS linking offered by Spring Framework.
At first, the components registered in the description are introduced.
Spring Framework sends and receives messages through JMS API using interfaces and classes shown below.
•javax.jms.ConnectionFactory
•javax.jms.Destination
•javax.jms.MessageProducer
•javax.jms.MessageConsumer
•javax.jms.Message
An interface which shows the message that retains header and body.
Messages are sent and received by implementation class of the interface.
•org.springframework.messaging.Message
A class which creates templates for generation and release of resources for using JMS API.
The implementation can be simplified by using a message sending and message synchronous
receiving function.
Basically, JmsMessagingTemplate which can handle
org.springframework.messaging.Message is used.
Since JmsMessagingTemplate wraps JmsTemplate, configuration can be done by using
JmsTemplate property.
However, sometimes it is preferable to use a JmsTemplate as it is. Specific use cases are explained
later.
•org.springframework.jms.listener.DefaultMessageListenerContainer
•@org.springframework.jms.annotation.JmsListener
1656 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
The flow of the process to send messages synchronously is explained using the figure.
Sr.No. Description
(1) Execute the process in Service by passing “Destination name for sending” and “payload of
messages to be sent” for JmsMessagingTemplate.
JmsMessagingTemplate delegates the process to JmsTemplate.
The flow of the process to receive messages asynchronously is explained using a figure.
1658 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
The flow of the process to receive messages synchronously is explained using a figure.
• Sharing model
• When client of sending and receiving side does not provide a model
model project is added and Jar file is distributed in the client for communication.
model project, and, relation between distributed archive file and existing project are as below.
1660 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) web project Place the listener class to receive messages asynchronously.
(2) domain project Place the Service executed from listener class for receiving
messages asynchronously.
Besides, Repository etc are same as used conventionally.
(3) model project or Jar file A class which is shared between the clients is used among
the classes belonging to domain layer.
For detail addition method, refer Project configuration is changed for SOAP server. of SOAP Web Service
(Server/Client). shared by JavaBean in the same way.
Settings that are common for both sending and receiving messages
This section explains common settings required for sending and receiving messages.
spring-jms of Spring Framework is added to pom.xml of domain project in order to use component for
linking JMS of Spring Framework.
• [projectName]-domain/pom.xml
<dependencies>
</dependencies>
Note: In the configuration example above, dependent library version is assumed to be managed by the
parent project. Hence, <version> element is not specified. Since dependent library above is defined by
Spring IO Platform, it is not necessary to specify the version by pom.xml.
Configuration of ConnectionFactory
ConnectionFactory definition method consists of two methods - a method defined by application server
and a method defined by Bean definition file.
A method defined by application server is selected to make Bean definition file independent of JMS provider
unless there is a specific reason to do otherwise.
This chapter explains a method defined by application server only.
Configuration for using a JavaBean fetched in Bean definition file through JNDI must be performed in order to
use ConnectionFactory defined in the application server.
1662 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
When JNDI is not used, ConnectionFactory can be used even when a Bean is defined for implemen-
tation class of ConnectionFactory. In such a case, implementation class of ConnectionFactory
is dependent on JMS provider. For details, refer JMS provider dependent configuration “configuration
when JNDI is not used”.
Configuration of DestinationResolver
Name resolution of Destination consists of 2 methods - Resolution by JNDI and Resolution by JMS provider.
Resolution by JMS provider is performed by default, however resolution by JNDI is recommended for portability
and control if there are no particular issues to address.
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
(2) When there is no prefix in JNDI name (java:comp/env/), set truewhen it is auto-assigned.
Default value is false.
A method to synchronously send messages from client (Producer) to JMS provider, for PTP model is explained
below.
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) -->
<property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) -->
<property name="sessionCacheSize" value="1" /> <!-- (3) -->
</bean>
1664 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(2) Specify JMS provider specific ConnectionFactory which is looked up by Bean definition
or JNDI name.
sessionTransacted - false
3.
Specify whether the transaction control is performed
in the session.
In this guideline, false is recommended as a
default for using transaction control described later.
messageConverter - SimpleMess
4.
is used.
Specify message converter.
Default can be used for the scope described in the
guideline.
destinationResolver -
5.
Specify DestinationResolver.
DynamicDes
In this guideline, it is recommended to use
is used.
JndiDestinationResolver which performs
(When
name resolution in JNDI.
DynamicDes
is used,
name
resolution
of Desti-
nation is
performed
by JMS
provider.)
deliveryMode - 2(PERSISTEN
7.
Select 1 (NON_PERSISTENT) or 2(PERSISTENT)
for delivery mode.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(*1)org.springframework.jms.support.converter.SimpleMessageConverter
(*2)org.springframework.jms.support.destination.DynamicDestinationResolver
• [projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;
import java.io.Serializable;
// omitted
// omitted
// omitted
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import com.example.domain.model.Todo;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate; // (1)
@Override
public void sendMessage(String message) {
}
}
1668 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
org.springframework.jms.support.converter.SimpleMessageConverter
is used in the default conversion.
When SimpleMessageConverter is used, the class which implements
javax.jms.Message, java.lang.String, byte array, java.util.Map and
java.io.Serializable interface can be sent.
As covered in JMS (Java Message Service) introduction, exceptions are converted to run-time exceptions
in Spring Framework. Hence, a run-time exception must be handled while handling JMS exception in
business logic.
convertJmsException method of
JmsMessagingTemplate MessagingException(*1)
JmsMessagingTemplate
and its
sub-exception
(*1) org.springframework.messaging.MessagingException
(*2) org.springframework.jms.JmsException
Header attribute can be edited and then sent synchronously by specifying header attribute and value of Key-
Value format in convertAndSend method argument of JmsMessagingTemplate. For header details, refer
javax.jms.Messages. An implementation example wherein JMSCorrelationID of role which links sending
and response messages is specified at the time of synchronous sending.
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import java.util.Map;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.jms.support.JmsHeaders;
import com.example.domain.model.Todo;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate;
jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue",
todo, headers); // (2)
}
}
1670 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Create header information by configuring header attribute name and its value, for
implementation class of Map.
(2) Synchronously send the message assigned with header information created in (2) by using
convertAndSend method of JmsMessagingTemplate.
Transaction control
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-domain.xml
An implementation example is shown below wherein Todo object is synchronously sent to Queue by performing
transaction control.
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
@Service
@Transactional("sendJmsTransactionManager") // (1)
public class TodoServiceImpl implements TodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate;
@Override
public void sendMessage(String message) {
1672 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
In the application wherein DB transaction control is performed, a transaction control policy must be determined
after reviewing relation between JMS and DB transaction based on business requirements.
• When JMS and DB transactions are to be separated and then committed and rolled back
A transaction boundary is declared individually when JMS and DB transactions are to be separated.
An implementation example is shown below wherein sendJmsTransactionManager of Transaction
control is used in JMS transaction control whereas transactionManager defined in default
configuration of Blank project is used in DB transaction control.
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TransactionalTod
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
@Service
@Transactional("sendJmsTransactionManager") // (1)
public class TransactionalTodoServiceImpl implements TransactionalTodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate;
@Inject
TodoService todoService;
@Override
public void sendMessage(String message) {
Todo todo = new Todo();
// omitted
jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo);
// omitted
todoService.update(todo);
}
• src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
@Transactional // (2)
@Service
public class TodoServiceImpl implements TodoService {
@Override
public void update(Todo todo) {
// omitted
}
}
1674 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• When JMS and DB transactions are to committed and rolled back together
A method which uses global transaction by JTA exists for linking JMS and DB transactions, however “Best
Effort 1 Phase Commit” is recommended since overheads are likely to occur for performance, among
protocol characteristics. Refer below for details.
Warning: When transaction process results are not returned in JMS provider due to issues like
loss of connection with JMS provider after receiving a message
When transaction process results are not returned in JMS provider due to issues like loss of connection
with JMS provider after receiving a message, transaction handling depends on JMS provider. Hence, a
design considering loss of received messages must be carried out. Especially, when loss of messages
is absolutely not permissible, providing a system to compensate for the loss of messages or using a
global transaction etc must be adopted.
– xxx-env.xml
• [projectName]-domain/src/main/java/com/example/domain/service/todo/ChainedTransacti
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
@Service
@Transactional("sendChainedTransactionManager") // (1)
public class ChainedTransactionalTodoServiceImpl implements ChainedTransactionalTodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate;
@Inject
TodoSharedService todoSharedService;
@Override
public void sendMessage(String message) {
Todo todo = new Todo();
// omitted
// omitted
todoSharedService.insert(todo); // (3)
}
1676 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(3) Execute process associated with DB access. In this example, SharedService is executed along
with DB update.
Note: If it is necessary to manage multiple transactions together like JMS and DB, for business, a global
transaction is considered. For global transactions, refer “when transaction control (global transaction con-
trol) is necessary for multiple DB (multiple resources)” of Settings for using transaction management.
As described in “message receiving method” of What is JMS, asynchronous receiving is generally used for
receiving messages.
Asynchronous receiving can be achieved by registering a listener method assigned by @JmsListener
annotation for DefaultMessageListenerContainer responsible for asynchronous receiving function
Roles of listener method which perform s processing at the time of asynchronous receiving are shown below.
• [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
1678 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
xsi:schemaLocation
Messages can be asynchronously received from specified Destination by specifying @JmsListener an-
notation in the component method managed by DI container. Implementation method is as shown below.
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.example.domain.model.Todo;
@Component
public class TodoMessageListener {
(1) Specify @JmsListener annotation for a method, for asynchronous receiving. Specify
Destination name for receiving in destination attribute.
A list of main attributes of @JmsListener annotation is shown below. For details and other attributes,
refer Javadoc of @JmsListener annotation.
1680 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
containerFactory
2.
Specify Bean name of
DefaultJmsListenerContainerFactorywhich manages the
listener method.
Default is jmsListenerContainerFactory.
selector
3.
Specify a message selector which acts as a condition for restricting the
message to be received.
When the value is not explicitly specified, default is “”(blank character)
and all the messages can be received.
For how to use, refer When messages which are asynchronously received
are to be restricted.
concurrency
3.
Specify upper limit of parallel numbers for listener method.;
Default value for concurrency attribute is 1.
Lower limit and upper limit of parallel numbers can be specified. For
example, specify “5-10” when lower limit is 5 and upper limit is 10.
When the parallel number of listener method has reached specified upper
limit, parallel processing is not done and a “waiting” state is reached.
Value should be specified as required.
When the header information of messages is to be used in the listener method such as sending process results of
asynchronous receiving to the Destination specified on Producer side (Value of header attribute JMSReplyTo),
@org.springframework.messaging.handler.annotation.Header annotation is used.
Implementation example is shown below.
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public JmsResponse<Todo> receiveAndResponse(
Todo todo, @Header("jms_replyTo") Destination storeResponseMessageQueue) { // (1)
// omitted
(1) Specify @Header annotation to fetch value of header attribute JMSReplyTo of receiving
message.
For a key specified while fetching JMS standard header attribute, refer JmsHeaders constants
definition.
A method which sends the process results of method defined in @JmsListener annotation, to Destination as a
response message.
Following 2 methods can be listed for specifying sending destination of process results.
Process results can be sent as messages to a fixed destination by defining @SendTo annotation which
specifies a destination, for a method defined by @JmsListener annotation.
Implementation example is as shown below.
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
@JmsListener(destination = "jms/queue/TodoMessageQueue")
@SendTo("jms/queue/ResponseMessageQueue") // (1)
public Todo receiveMessageAndSendTo(Todo todo) {
// omitted
return todo; // (2)
}
1682 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Default sending destination of process results can be specified by defining @SendTo
annotation.
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public JmsResponse<Todo> receiveMessageJmsResponse(Todo todo) {
// omitted
if (todo.isFinished()) {
resQueue = "jms/queue/FinihedTodoMessageQueue";
} else {
resQueue = "jms/queue/ActiveTodoMessageQueue";
}
(1) When Queue for sending is to be changed in accordance with the process details,
forDestination or forQueue methods of JmsResponse class are used.
In this example, messages are sent from Destination name by using forQueue method.
Using the implementation below, messages of process results can be sent to any destination specified on Producer
side.
Implementation
Implementation details
location
Producer Specify Destination in header attribute JMSReplyTo of messages in accordance with JMS
side standards.
For editing of header attribute, refer :ref‘JMSHowToUseSettingForSendWithHeader‘.
Header attribute JMSReplyTo is given priority over the default Destination specified on the Consumer side. For
details, refer Response management.
The messages to be received can be restricted by specifying a message selector at the time of receiving.
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
1684 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
An input check must be carried out to ensure that the messages which retain invalid data are not processed in the
business logic from a security viewpoint.
Input check is implemented in Service method by using Method Validation and the exception at the time of input
check error is handled by the listener method.
This is done to avoid the occurrence of unnecessary rollback process for the exception occurring at the time of
input check error, while performing transaction control. For transaction control, refer Transaction control.
For details of how to configure and implement Method Validation, refer Method Validation of Input Validation.
An implementation example is shown below wherein input check is performed for Todo object shown in Basic
synchronous sending.
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import javax.validation.Valid;
import org.springframework.validation.annotation.Validated;
import com.example.domain.model.Todo;
@Validated // (1)
public interface TodoService {
(1) Declare the interface as a target for input check by attaching a @Validated annotation.
(2) Specify a constraint annotation of Bean Validation as an argument for the method.
• [projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;
import java.io.Serializable;
import javax.validation.constraints.Null;
// (1)
public class Todo implements Serializable {
// omitted
@Null
private String description;
// omitted
// omitted
1686 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@Inject
TodoService todoService;
@JmsListener(destination = "jms/queue/MessageQueue")
public void receive(Todo todo) {
try {
todoService.updateTodo(todo); // (1)
} catch (ConstraintViolationException e) { // (2)
// omitted
}
}
Transaction control
Note: When message returns to Queue, it is asynchronously received once again. Since the cause of error
is not resolved, the operations of rollback and asynchronous receiving are repeated. A threshold value for
number of resends after rollback can be set depending on JMS provider and when resend count exceeds the
threshold value, the message is stored in Dead Letter Queue.
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-domain.xml
• [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
1688 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
transaction-manager
Warning: Since Connection or Session caching in the application is likely to be prohibited de-
pending on the application server, cache validation and invalidation should be determined in accordance
with the specifications of application server to be used.
Note: A method which performs exception handling other than roll back process in case of specific excep-
tions
When transaction control is enabled, the message returns to Queue due to roll back if the exception occurred
in input check is thrown without getting captured. Since listener method asynchronously receives the message
again which has returned in Queue, the sequence asynchronous receiving Error occurrence Rollback is repeated a
number of times for JMS provider configuration. In case of an error for which the cause of the error is not resolved
even after retry, the error handling is done so as not to throw an exception from listener method after capturing,
to restrain futile processes mentioned above. For details, refer Exception handling at the time of asynchronous
receiving.
In the application wherein DB transaction control is required, a transaction control policy must be determined
after reviewing the relation between JMS and DB transactions based on business requirements.
• When the transaction is to be committed and rolled back by separating JMS and DB transactions
In some cases, JMS transaction is rolled back whereas only DB transaction is committed.
– [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
package com.example.listener.todo;
import javax.inject.Inject;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.example.domain.service.todo.TodoService;
import com.example.domain.model.Todo;
@Component
public class TodoMessageListener {
@Inject
TodoService todoService;
todoService.update(todo);
}
}
– [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceIm
package com.example.domain.service.todo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;
@Transactional // (2)
@Service
public class TodoServiceImpl implements TodoService {
@Override
public void update(Todo todo) {
// omitted
}
}
1690 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Nesting sequence for transaction boundary depends on the business requirements and JMS
provider is often used for linking with external systems. In such a case, JMS transaction boundary
is kept outside DB transaction boundary and recovery becomes easier when inward DB transaction is
completed earlier. When DB transaction is committed and JMS transaction is rolled back, the message
returns to the Queue. Hence the same message is processed again. It should be designed so as to allow
DB update process to be re-tried at the time of reexecution of business process.
• When JMS and DB transactions are to be committed and rolled back together
A method which uses global transaction by JTA exists for linking JMS and DB transactions, however “Best
Effort 1 Phase Commit” is recommended since overheads are likely to occur for performance, among
protocol characteristics. Refer below for details.
Warning: When transaction process results are not returned in JMS provider due to issues like
loss of connection with JMS provider after receiving a message
When transaction process results are not returned in JMS provider due to issues like loss of connection
with JMS provider after receiving a message, transaction handling depends on JMS provider. Hence,
a design considering loss of received messages, reprocessing of messages due to roll back must be
performed. Especially, when loss of messages is absolutely not permissible, providing a system to
compensate for the loss of messages or using a global transaction etc must be adopted.
– [projectName]-env/src/main/resources/META-INF/spring/[projectName]-env.xml
– [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
1692 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
concurrency="1"
error-handler="jmsErrorHandler"
cache="consumer"
transaction-manager="chainedTransactionManager"
acknowledge="transacted"/>
factory-id
transaction-manager
acknowledge
Note: In DefaultMessageListene
when Bean of implementation
org.springframework.transaction.support.
is specified in transaction-managerattribute,
action control which uses this Bean is enabled.
since ChainedTransactionManager does no
ResourceTransactionManager, transaction contr
abled. transacted must be specified in acknowl
in order to enable transaction control. Accordingly, Se
a target for transaction control is generated and transact
ChainedTransactionManager is enabled.
– [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
@Inject
TodoService todoService;
Behaviour when an application is created in accordance with the configuration and implementation example
above is explained.
1694 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
– When connection with JMS provider is lost after receiving the message and only DB transaction
is committed
JMS transaction handling is dependent on JMS provider, however, since DB transaction is committed,
an inconsistency is likely to occur in JMS and DB status.
Considering the possibility of rollback of JMS transaction, integrity of the data must be ensured when
same message is received multiple times.
An example to ensure data integrity is shown below.
– When the process after asynchronous receiving is executed multiple times, process must be de-
signed to ensure same status after the processing.
1696 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: When it is necessary to stringently control multiple transactions like JMS and DB by
avoiding events given above, use of global transaction is considered. For global transaction, refer
various product manuals.
While performing transaction control, exceptions must be handled considering the rollback process.
For details of transaction control, refer Transaction control.
Exception handling of JMS is classified in 2 patterns given below based on the objective.
(1) When exceptions occurring in business Business exception like Listener Listener
layer are to be individually handled input check error method method unit
(try-catch)
The exceptions occurring in the business layer like “message contents are invalid” are trapped (try-catch)
by listener method and handled by listener method unit.
While performing transaction control, since exceptions must be thrown in
DefaultMessageListenerContainer for the cases which require rollback, the captured
exception must be thrown again.
Implementation example is shown below.
– [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
@Inject
TodoService todoService;
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public JmsResponse<Todo> receiveTodo(Todo todo) {
try {
todoService.insertTodo(todo);
} catch (BusinessException e) {
return JmsResponse.forQueue(todo, "jms/queue/ErrorMessageQueue"); // (1)
1698 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
}
return null; // (2)
}
(1) An object can be sent to a Queue for storing a logical error message by using forQueue
method of JmsResponse class.
In this example, since BusinessException which outputs the log in AOP is captured,
log output process is not described explicitly. However, exceptions must be handled so as
not to eliminate the cause of exception.
When the messages are to be processed after roll back, by performing transaction control,
the exception thus captured must be thrown.
(2) When the messages are not sent, set return value to null.
– [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
– [projectName]-web/src/main/java/com/example/listener/todo/JmsErrorHandler.java
package com.example.listener.todo;
import org.springframework.util.ErrorHandler;
import org.terasoluna.gfw.common.exception.SystemException;
@Override
public void handleError(Throwable t) { // (2)
// omitted
if (t.getCause() instanceof SystemException) { // (3)
} else {
// omitted error handling
}
}
}
1700 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(3) Determine any exception class and implement error handling associated with the exception.
t.getCause() must be executed to fetch the exception occurred in the application.
• [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory"
class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) -->
<property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) -->
<property name="sessionCacheSize" value="1" /> <!-- (3) -->
</bean>
1702 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
sessionTransacted - false
3.
Set whether the transaction is controlled in the
session.
In this guideline, since transaction control described
later is used, default false is recommended.
sessionAcknowledgeMode -
4.
Set confirmation response mode of session for 1
sessionAcknowledgeMode.
For details, refer JavaDoc of JMSTemplate.
Todo
Details of sessionAcknowledgeMode will be added
later.
receiveTimeout -
5.
Set timeout period (milliseconds) at the time of 0
synchronous receiving. If it is not set, wait till the
message is received.
If the timeout period is not set, it impacts the
subsequent processes, hence an appropriate timeout
period must always be set.
messageConverter - SimpleMess
6.
is used.
Set message converter.
Default can be used in the range introduced in the
guideline.
8.2. JMS(Java Message Service) 1703
destinationResolver -
7.
Set DestinationResolver.
DynamicDes
This guideline recommends setting
is used.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(*1)org.springframework.jms.support.converter.SimpleMessageConverter
(*2)org.springframework.jms.support.destination.DynamicDestinationResolver
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import com.example.domain.model.Todo;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
JmsMessagingTemplate jmsMessagingTemplate;
@Override
public String receiveTodo() {
// omitted
Todo retTodo = jmsMessagingTemplate.receiveAndConvert("jms/queue/TodoMessageQueue", To
}
}
1704 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
8.2.3 Appendix
Configuration varies for each JMS provider. Configuration for each JMS provider is explained below.
– $CATALINA_HOME/bin/setenv.sh
# omitted
# (1)
-Dorg.apache.activemq.SERIALIZABLE_PACKAGES=java.lang,java.util,org.apache.activemq,org.f
# omitted
• Adding a library
activemq-client is added to pom.xml of domain project and web project as a library for build.
Further, activemq-client and its dependent libraries are added to the application server.
– [projectName]-domain/pom.xml
– [projectName]-web/pom.xml
<dependencies>
</dependencies>
(1) Add client library of Apache ActiveMQ to dependencies as a build. Since JMS API is also
incorporated in activemq-client library, it is not necessary to add JMS API as a
library.
Note: In the configuration example above, the version of dependent library is assumed to be managed by
the parent project. Hence, <version> element is not specified. Since dependent libraries given above
are defined by Spring IO Platform, the version is not required to be specified by pom.xml.
Warning: The version of the library used while establishing a connection with Apache ActiveMQ is
defined in Spring IO Platform which is used by TERASOLUNA Server Framework for Java. Hence,
care must be taken while determining Apache ActiveMQ version. Note that, library and middleware
versions are likely to be inconsistent while upgrading the version of TERASOLUNA Server Framework
for Java.
1706 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
For registration of JNDI to application server, refer Manually integrating Tomcat and ActiveMQ.
– [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
(2) Specify beginning URL of Apache ActiveMQ. Beginning URL sets the value for each
environment.
If JmsMessageTemplate is to be used for mass mailing of identical message, memory usage is likely to
increase.
Hence, a method wherein implementation is done by using send method of JmsTemplate class must be
considered.
Reason being, an instance of org.springframework.jms.core.MessageCreator class is generated
while sending a message in JmsMessageTemplate.
In order to prevent the generation of unnecessary instance, the messages are sent by send method of
JmsTemplate class wherein MessageCreator instance is not generated during sending thus reducing the
amount of memory used.
An example of code wherein a string is sent 100 times to an identical destination is shown below.
• [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.
package com.example.domain.service.todo;
import java.io.IOException;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
JmsTemplate jmsTemplate; // (1)
@Override
public void sendManyMessage(final String messageStr) throws IOException {
MessageCreator mc = new MessageCreator() { // (2)
public Message createMessage(Session session) throws JMSException {
TextMessage message = session.createTextMessage(); // (3)
message.setText(messageStr);
// omitted
return message;
}
};
for (int i = 0; i < 100; i++) {
jmsTemplate.send("jms/queue/TodoMessageQueue", mc); // (4)
}
1708 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
}
}
(3) When messages are sent by send method of JmsTemplate class, instances of
MessageCreator are no longer generated for each loop and the amount of memory can be
reduced.
While handling data of large size like Image data (Data of size 1MB or more as a rough indication),
OutOfMemoryError is likely to occur due to number of concurrent transactions and heap size. In standard
API of JMS, only StreamMessage which sends primitive type data and ByteMessage which can send unin-
terpreted byte stream can handle large size data as a stream. Hence, a specific API offered for each JMS provider
is used instead of JMS API.
A message of large size can be received or sent by using Blob Message. Implementation example is shown below.
• Configuration
While sending a message by using BlobMessage, the message is stored in the server which is temporarily
run by Apache ActiveMQ, instead of heap area. Definition example for storage destination for the messages
is shown below.
– [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="connectionFactory"
class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<!-- (1) -->
<value>tcp://localhost:61616?jms.blobTransferPolicy.uploadUrl=/tmp</value>
</property>
</bean>
(1) Define a directory of Apache ActiveMQ server which temporarily stores messages.
http://localhost:8080/uploads/ is set as a default in
jms.blobTransferPolicy.uploadUrl wherein location for temporary file can be
specified by overloading default or brokerURL.
For example, the file is temporarily stored in /tmp.
• Sending
An implementation example of sending class which use Blob Message is shown below.
– [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceIm
package com.example.domain.service.todo;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.Session;
import org.apache.activemq.ActiveMQSession;
import org.apache.activemq.BlobMessage;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;
import org.springframework.stereotype.Service;
@Service
public class TodoServiceImpl implements TodoService {
@Inject
JmsTemplate jmsTemplate;
@Override
public void sendBlobMessage(String inputFilePath) throws IOException {
1710 8 Messaging
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• Receiving
– [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.ja
package com.example.listener.todo;
import java.io.IOException;
import javax.inject.Inject;
import javax.jms.JMSException;
import org.apache.activemq.BlobMessage;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.example.domain.service.todo.TodoService;
@Component
public class TodoMessageListener {
@Inject
TodoService todoService;
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public void receiveBlobMessage(BlobMessage message) throws IOException, JMSException
todoService.fileInputBlobMessage(message);
// omitted
}
}
– [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceIm
package com.example.domain.service.todo;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.activemq.BlobMessage;
import org.springframework.stereotype.Service;
@Service
public class TodoServiceImpl implements TodoService {
@Override
public void fileInputBlobMessage(BlobMessage message) throws IOException {
try(InputStream is = message.getInputStream()){ // (1)
Path path = Paths.get("outputFilePath");
Files.copy(is, path);
// omitted
}
}
}
1712 8 Messaging
1713
Security countermeasures
Spring Security is a framework which is used while implementing the security countermeasure function in the ap-
plication. Spring Security can also be used in stand-alone application, but generally it is used while implementing
the security countermeasures for Web application deployed in servlet container. This chapter describes only those
functions provided by Spring Security which are considered to be frequently used in general Web application.
Spring Security also provides many functions not introduced in this guideline. Refer Spring Security Reference to
know all functions provided by Spring Security.
This guideline assumes the use of Spring Security 4.0 or higher version. Various changes have been applied for
upgrading version of Spring Security to 4.0 and the samples described later also have used Spring Security 4.
Refer Migrating from Spring Security 3.x to 4.x (XML Configuration) for the changes.
Spring Security provides the following functions as the basic functions of security countermeasures.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Function Description
Authentication function A function that checks whether the application user is valid.
Authorization function A function that controls access to the resources and processes provided by the appli-
cation.
Spring Security provides several functions besides the basic functions of authentication and authorization to en-
hance Web application security.
Function Description
Session management func- A function that protects the user from session hijacking attack and session fixation
tion attack, A function to control session lifecycle (generate, discard, time out).
CSRF countermeasure func- A function to protect the user from Cross site request forgeries (CSRF) attack.
tion
Security header output func- A function to link with security countermeasure function of Web browser and protect
tion the user from the attack where the browser function is misused.
Overview of Spring Security architecture and the role of key components those configure Spring Security are
described before explaining each function in detail.
Note: The developers need not be directly aware of the contents described over here while using the default
operation provided by Spring Security as it is or while using the mechanism to support the configuration of Spring
Security. Therefore, this section can be skipped while reading when the user first wants to know how to use each
function.
However, since the contents described here are required while customizing the default operation of Spring Security,
it is recommended that the application architect should go through it once.
First, the module provided by Spring Security used as a framework stack is introduced.
Framework stack module is as follows. This guideline also describes the method to implement security counter-
measures using these modules.
The following modules those are not included in the framework stack, are also provided to support the authenti-
cation methods used in general. Use of these modules also needs to be reviewed as per the security requirements.
Test module
A module to support the test has been added from Spring Security 4.0.
Framework processing
Spring Security has adopted the architecture that implements security countermeasures for Web application using
the servlet filter mechanism and executes the processes in the following flow.
The key component that configures the framework process for the Web application is as follows. For details, refer
Spring Security Reference -The Security Filter Chain-.
FilterChainProxy
FilterChainProxy class is a servlet filter class that is an entry point of the framework process for the Web
application. This class controls the entire flow of the framework process and assigns specific security countermea-
sure process to Security Filter.
HttpFirewall
SecurityFilterChain
SecurityFilterChain interface manages Security Filter list to be applied to the request received by
FilterChainProxy. By default, DefaultSecurityFilterChain class is used and the Security Fil-
For example, security countermeasures for the details those vary according to the URL can be applied if the bean
is defined as follows.
<sec:http pattern="/api/**">
<!-- ... -->
</sec:http>
<sec:http pattern="/ui/**">
<!-- ... -->
</sec:http>
Security Filter
Security Filter class is a servlet filter class that provides a process required to implement framework function and
security countermeasure function.
Spring Security is a mechanism to implement the security countermeasures for the Web application by linking
multiple Security Filters. Here, core class required to implement authentication and authorization functions is
introduced. For details, refer Spring Security Reference -Core Security Filters-.
Here, the simplest method of setup that applies Spring Security to the Web application and displays the default
login screen provided by Spring Security is explained. Customization method and extension method required in
real application development are described sequentially in the following sections.
Note: When the development project is created from Blank project , the setting described here is already config-
ured. For how to create a development project, refer ‘Create Web application development project‘ .
First, the common library that uses Spring Security as dependency is applied. Refer Building blocks of Common
Library for the relation between Spring Security and common library.
This guideline assumes that the development project is created using Maven.
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-core</artifactId> <!-- (1) -->
</dependency>
<dependency>
<groupId>org.terasoluna.gfw</groupId>
<artifactId>terasoluna-gfw-security-web</artifactId> <!-- (2) -->
</dependency>
Note: This guideline assumes that the library version is managed using Spring IO Platform, therefore
<version> element is omitted.
XML file is created as below to define a bean for the component of Spring Security. (extracted from Blank project)
</beans>
When the static resources like CSS are used in JSF, the access rights must be assigned to the folder where they are
stored. Refer Settings not to apply security countermeasures for details.
Define to generate DI container of Spring using a bean definition file thus created.
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
Finally, the servlet filter class (FilterChainProxy) provided by Spring Security is registered in servlet con-
tainer.
The resource path (like the path to access css file or image file) for which the security countermeasures are
not required, can be controlled using <sec:http> tag so that the security function (Security Filter) of Spring
Security is not applied.
(1) Specify the pattern of the path where the security function is not applied in pattern attribute.
9.2 Authentication
9.2.1 Overview
The standard method to check the validity of a user is to register the users who can use the application in a data
store and and verify the authentication information (user name, password etc) input by the user. Although a
relational database is commonly used in a data store wherein user information is registered, directory service or
external system etc are also used.
Further, there are several methods which ask a user to input authentication information. Although a method
wherein input form of HTML or a method wherein an authentication method of HTTP standard defined in RFC
(Basic authentication or Digest authentication etc) are used in general, the methods like OpenID authentication or
single sign-on authentication are also used.
This section introduces an implementation example wherein the authentication is done by verifying authentication
information input in HTML input form and user information stored in the relational database, and explains how
to use authentication function of Spring Security.
(1) Client specifies credentials (user name and password) for the path wherein authentication is
performed and sends a request.
(2) Authentication Filter fetches credentials from the request and calls authentication process of
AuthenticationManager class.
Authentication Filter
Authentication Filter is a servlet filter which offers implementation for the authentication method. Primary au-
thentication methods supported by Spring Security are as given below..
Fetch credentials from HTTP request parameter in servlet filter class for form
UsernamePasswordAuthenticationFilter
authentication.
Fetch credentials from authentication header of HTTP request in servlet filter class
BasicAuthenticationFilter
for Basic authentication.
Fetch credentials from authentication header of HTTP request in servlet filter class
DigestAuthenticationFilter
for Digest authentication.
Fetch credentials from Cookies of HTTP request in servlet filter class for Remember
RememberMeAuthenticationFilter
Me authentication.
If Remember Me authentication is enabled, user stays logged in even if browser is
closed and a session timeout has occurred.
These servlet filters are one of the Authentication filters introduced in Framework processing.
Note: When an authentication process not supported by Spring Security must be adopted, it must be incorporated
in Spring Security after creating a Authentication Filter for adopting this authentication method.
AuthenticationManager
AuthenticationProvider
Note: When an authentication process not offered by Spring Security must be adopted, it must be incorporated
in Spring Security after creating an AuthenticationProvider for implementing authentication process.
Bean definition example and implementation method required for using authentication function is explained.
This section, as explained in Overview, explains a method to carry out authentication process by verifying creden-
tials input in HTML input form and user information stored in the relational database.
Form authentication
Spring Security performs form authentication as per the flow given below.
(1) Client sends credentials (user name and password) as request parameters for the path which carries
out form authentication.
<sec:http>
<sec:form-login /> <!-- (1) -->
<!-- omitted -->
</sec:http>
An auto-config attribute which specifies whether the settings for form authentication
(<sec:form-login> tag), Basic authentication (<sec:http-basic> tag) and logout (<sec:logout>
tag) are performed automatically, is provided in <sec:http>. Default value is false (not set automatically)
and it is recommended to use default value even in the reference document of Spring Security.
Further note that when auto-config is not defined, form authentication (<sec:form-login> tag) or
Basic authentication (<sec:http-basic> tag) must be defined. This is required to meet the specifica-
tion of Spring Security wherein a Bean must be defined for one or more Authentication Filters in a single
SecurityFilterChain (<sec:http>).
Default operation
If user account is accessed for "/login" using GET method in the default operation of Spring Security, a default
login form provided by Spring Security is displayed and when login button is clicked, "/login" is accessed by
POST method and authentication process is carried out.
Although a login form for form authentication is provided in Spring Security as a default, it is rarely used as it is.
Below, a method wherein an auto-created login form is applied in Spring Security is explained.
At first, a JSP for displaying a login form is created. An implementation example wherein a login form is displayed
after receiving a request in Spring MVC is given below.
</tr>
</table>
</form:form>
</div>
<%-- omitted --%>
(2) Output an exception message which is output at the time of authentication error.
It is recommended to output by using <t:messagesPanel> tag provided by a common library.
For how to use <t:messagesPanel> tag, refer “Message Management”.
Note that, when an authentication error occurs, exception object is stored in the session or request
scope with the attribute name "SPRING_SECURITY_LAST_EXCEPTION".
<sec:http>
<sec:form-login
login-page="/login/loginForm"
login-processing-url="/login" /> <!-- (1)(2) -->
<sec:intercept-url pattern="/login/**" access="permitAll"/> <!-- (3) -->
<sec:intercept-url pattern="/**" access="isAuthenticated()"/> <!-- (4) -->
</sec:http>
(3) Assign the rights enabling access to all users for the location under /login path where login form is
stored.
For how to specify access policy for web resource, refer “Authorization”.
(4) Assign the access rights for web resource handled by the application.
In the example above, only authenticated users are granted the rights to access location under root
path of web application.
For how to specify access policy for web resource, refer “Authorization”.
Default values of following configuration are changed from Spring Security version 4.0
• username-parameter
• password-parameter
• login-processing-url
• authentication-failure-url
Spring Security offers AuthenticationSuccessHandler interface and implementation class as the com-
ponents to control the response when the authentication is successful.
Default operation
In the default operation of Spring Security, the request that was denied prior to authentication is saved in HTTP
session and when the authentication is successful, the request which was denied access is restored and redirected.
Page is displayed if the authenticated user has the access rights for redirected page, authentication error occurs if
the user does not have the access rights. SavedRequestAwareAuthenticationSuccessHandler class
is used to carry out this operation.
Since the transition destination after explicitly displaying login form and performing authentication is the root
path of web application ("/"), as per default configuration of Spring Security, user is redirected to the root path
of Web application when the authentication is successful.
Spring Security offers AuthenticationFailureHandler interface and implementation class as the com-
ponents to control the response when the authentication fails.
Default operation
In the default operation of Spring Security, user is redirected to a URL assigned with a query parameter
"error"in a path which displays login form.
As an example, when the path to display login form is "/login", the user is redirected to "/login?error".
DB authentication
(1) Spring Security receives authentication request from the client and calls authentication process of
DaoAuthenticationProvider.
(3) Implementation class of UserDetailsService fetches user information from data store.
Spring Security offers an implementation class to fetch user information from relational database through JDBC.
• org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl (Implemen-
tation class of UserDetailsService)
Since these implementation classes only perform authentication processes of minimum level (password verifica-
tion, determination of user validity), they are used as it is very rarely. Hence, this guideline explains how to create
implementation classes of UserDetails and UserDetailsService.
Creating UserDetails
UserDetails is an interface which provides credentials (user name and password) and user status necessary
in the authentication process and defines following methods. When DaoAuthenticationProvider is used
as AuthenticationProvider, implementation class of UserDetails is created in accordance with the
requirements of application.
UserDetails interface
(3) isEnabled Determine whether the user is valid. Return true in case of valid user.
When the user is invalid, DaoAuthenticationProvider throws
DisabledException.
(4) isAccountNonLocked Determine locked status of the account. When the account is not locked,
return true.
When the account is locked, DaoAuthenticationProvider throws
LockedException.
(5) Determine expiry status of the account. Return true when the account
isAccountNonExpired
is not expired.
When the account is expired, DaoAuthenticationProvider
throws AccountExpiredException.
When the user wants to change the screen transition for each exception thrown by
DaoAuthenticationProvider, ExceptionMappingAuthenticationFailureHandler must be
used as AuthenticationFailureHandler.
As an example, when user wants to transit to password change screen in case of password expiry for
the user, screen transition can be changed by handling CredentialsExpiredException by using
ExceptionMappingAuthenticationFailureHandler.
Here, an implementation class of UserDetails which retains account information is created. This example can
also be implemented by inheriting User. However, here an example wherein UserDetails is implemented is
introduced.
public AccountUserDetails(
Account account, Collection<GrantedAuthority> authorities) {
// (2)
this.account = account;
this.authorities = authorities;
}
// (3)
public String getPassword() {
return account.getPassword();
}
public String getUsername() {
return account.getUsername();
}
public boolean isEnabled() {
return account.isEnabled();
}
public Collection<GrantedAuthority> getAuthorities() {
return authorities;
}
// (4)
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
// (5)
public Account getAccount() {
return account;
}
(4) Although the example of this sections does not implement check for, “Account locking”, “Account
expiry” and “Credentials expiry”, these must be checked in accordance with the requirements.
(5) Provide a getter method to enable access to account information in the process after successful
completion of authentication.
Spring Security provides User class as an implementation class of UserDetails. When you inherit a User
class, credentials and user status can be easily retained.
Creating UserDetailsService
UserDetailsService is an interface to fetch credentials and user status required for authentication pro-
cess from data store and defines following methods. When DaoAuthenticationProvider is used as
AuthenticationProvider, implementation class of UserDetailsService is created in accordance
with the requirements of the application.
• UserDetailsService interface
Here, a service class is created to search account information from the database and generate an instance
of UserDetails. In this sample, account information is fetched by using SharedService. For
SharedService, refer Implementation of Service.
// (1)
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
@Inject
AccountRepository accountRepository;
// (2)
@Override
public Account findOne(String username) {
Account account = accountRepository.findOneByUsername(username);
if (account == null) {
throw new ResourceNotFoundException("The given account is not found! username="
+ username);
}
return account;
}
}
(1) Create a class which implements AccountSharedService interface and assign @Service.
In the example above, AccountSharedServiceImpl is registered in DI container using
component scan function.
// (1)
@Service
@Transactional
public class AccountUserDetailsService implements UserDetailsService {
@Inject
AccountSharedService accountSharedService;
try {
Account account = accountSharedService.findOne(username);
// (2)
return new AccountUserDetails(account, getAuthorities(account));
} catch (ResourceNotFoundException e) {
// (3)
throw new UsernameNotFoundException("user not found", e);
}
}
// (4)
private Collection<GrantedAuthority> getAuthorities(Account account) {
if (account.isAdmin()) {
return AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN");
} else {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
}
}
(1) Create a class which implements UserDetailsService interface and assign @Service.
In the example above, UserDetailsService is registered in DI container using a component
scan function.
(4) Generate information about the rights (role) retained by the user. The rights (role) generated here are
used in the authorization process.
Authorization process of Spring Security handles the rights information starting with "ROLE_"as a role. Hence,
when resource access is to be controlled by using role, "ROLE_" must be assigned as a prefix to the rights
information which is being handled as a role.
In the default operation of Spring Security, error handling is performed after changing
UsernameNotFoundException to an exception called BadCredentialsException.
BadCredentialsException denotes an error in any field of the credentials specified by the client,
specific error causes are not notified to the client.
Applying DB authentication
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> <!-- (4) -->
(4) Define a Bean for PasswordEncoder to be used at the time of password verification.
In the example above, BCryptPasswordEncoder is defined which performs password hashing
by using BCrypt algorithm.
For password hashing, refer “Password hashing”.
Password hashing
When a password is stored in the database, password is not stored as it is and a hash value of the password is
generally stored.
Spring Security provides an interface and implementation class for password hashing and operates in coordination
with the authentication function.
• org.springframework.security.crypto.password.PasswordEncoder
• org.springframework.security.authentication.encoding.PasswordEncoder
When there are no specific constraints in the password hashing requirements, using the implementation class of
PasswordEncoder interface of org.springframework.security.crypto.password package is
recommended.
Note: For how to use deprecated PasswordEncoder, refer “Using PasswordEncoder of deprecated package”.
Spring Security offers following classes as the implementation class of PasswordEncoder interface.
This section explains how to use BCryptPasswordEncoderthat has been recommended by Spring Security.
BCryptPasswordEncoder
BCryptPasswordEncoder is an implementation class which performs password hashing and password ver-
ification by using BCrypt algorithm. 16 byte random numbers (java.security.SecureRandom) are used
in Salt, Stretching is done for 1024 (2 to the power of 10) times by default..
<bean id="passwordEncoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" > <!-- (1) -->
<constructor-arg name="strength" value="11" /> <!-- (2) -->
</bean>
(2) Specify number of rounds for stretching count of hashing in the argument of constructor.
This argument can be omitted and the values that can be specified are in the range from 4 to 31.
Note that default value is 10 when the argument value is not specified.
The explanation is omitted in the guideline, however
java.security.SecureRandom.SecureRandom can also be specified as a constructor
argument.
PasswordEncoder is used in the class which carries out a process by using BCryptPasswordEncoder,
by injecting from DI container.
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
@Inject
AccountRepository accountRepository;
@Inject
PasswordEncoder passwordEncoder; // (1)
account.setPassword(encodedPassword);
// omitted
return accountRepository.save(account);
}
Note: Salt
A salt is a string which is added to data targeted for hashing. Since number of digits exceed the actual password
length by assigning a salt to a password, password analysis using programs like rainbow crack become difficult.
Note that, it is recommended to set different values of salt (random values) for each user. This is necessary
because if identical salt value is used, the string (password) before hashing can be easily identified from the hash
value.
Note: Stretching
Information related to stored password is repeatedly encrypted by iterating hash function calculation in order to
extend the time required for password analysis as a countermeasure against password brute force attack. However,
stretching frequency must be determined considering the system performance since stretching is likely to impact
system performance.
In Spring Security, stretching is done for 1024 times (2 to the power of 10) by default, however this frequency can
be changed by constructor argument (strength). A value from 4 (16 times) to 31 (2, 147, 483, 648 times) can
be specified in the strength. More the frequency of stretching, stronger the password. However, it is likely to
impact performance due to higher complexity.
Spring Security offers a system which links process results of authentication process with other components using
an event notification system offered by Spring Security.
If this system is used, following security requirements can be incorporated in the authentication function of Spring
Security.
(1) Spring Security authentication function passes authentication results (authentication information and
authentication exception)
in AuthenticationEventPublisher and requests notification of authentication event.
Note: Memo
Events used by Spring Security can be classified into 2 types - the events which notify successful authentication
and the events which notify failure in authentication. Event class offered by Spring Security are explained below.
There are 3 main events which are notified by Spring Security at the time of successful authentication. These
events are notified in the following order unless an error occurs in between.
Main events which are notified by Spring Security at the time of authentication failure are as below. When
authentication fails, any one of the events is notified.
When you want to carry out a process by receiving authentication event notification, a class implementing a
method that assigns @EventListener is created and registered in DI container.
@Component
public class AuthenticationEventListeners {
@EventListener // (1)
public void handleBadCredentials(
AuthenticationFailureBadCredentialsEvent event) { // (2)
log.info("Bad credentials is detected. username : {}", event.getAuthentication().getName());
// omitted
}
(2) Specify an authentication event class which is to be handled in the method argument.
Logout
Spring Security performs logout process as per the flow given below.
(2) LogoutFilter calls LogoutHandler method and performs actual logout process.
Since LogoutHandlerautomatically sets the class which supports bean definition offered by Spring Security,
in LogoutFilter, there is no need for a developer to be specifically aware of the same. Note that, if Remember
Me authentication function is enabled, implementation class of LogoutHandler is also configured to delete the
token for Remember Me authentication.
<sec:http>
<!-- omitted -->
<sec:logout /> <!-- (1) -->
<!-- omitted -->
</sec:http>
Default values of following configuration changes from Spring Security 4.0 version.
• logout-url
Although description is omitted in the guideline, note that delete-cookies attribute exists in
<sec:logout> tag for deleting Cookie specified at the time of logout. However, it has been reported that a
cookie is sometimes not deleted even after using this attribute.
• https://jira.spring.io/browse/SEC-2091
Default operation
In the default operation of Spring Security, if a request is sent to the path "/logout", logout process is carried
out. Logout process consists of “clearing authentication information of logged in user” and “cancelling a session”.
Further,
• “Deleting token for CSRF measures” when CSRF measures are adopted
• “Deleting token for Remember Me authentication” when Remember Me authentication function is used
If CSRF measures are enabled, tokens for CSRF measures must be sent by using POST method.
Default operation
In default operation of Spring Security, user is redirected to a URL wherein a query parameter called "logout"is
assigned to the path for displaying login form.
As an example, when the path to display login form is "/login", the user is redirected to "/login?logout".
Authentication information of the authenticated user is stored in the session during default implementation of
Spring Security. Authentication information stored in the session is stored in SecurityContextHolder class
by SecurityContextPersistenceFilter class for each request and can be accessed from anywhere if it
is in the same thread.
A method wherein UserDetails are fetched from authentication information and the information retained in
the fetched UserDetails is accessed.
An audit log which records information like “When”, “Who”, “Which data” and “Type of access” is fetched
in the general business application. “Who” for fulfilling these requirements can be fetched from authentication
information.
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication(); // (1)
String userUuid = null;
if (authentication.getPrincipal() instanceof AccountUserDetails) {
AccountUserDetails userDetails =
AccountUserDetails.class.cast(authentication.getPrincipal()); // (2)
userUuid = userDetails.getAccount().getUserUuid(); // (3)
}
if (log.isInfoEnabled()) {
log.info("type:Audit\tuserUuid:{}\tresource:{}\tmethod:{}",
userUuid, httpRequest.getRequestURI(), httpRequest.getMethod());
}
User information of login user is displayed on the screen in general Web application. User information of login
user while meeting these requirements can be fetched from authentication information.
(1) Use <sec:authentication> tag offered by Spring Security and fetch authentication
information (Authentication object).
Specify path for the property to be accessed in property attribute.
When a nested object is to be accessed, property name should be joined by ".".
Although the implementation example for displaying user information which contains authentication information
is explained, it is possible to store value in any scope variable by combining var attribute and scope attribute.
When the display contents are to be changed according to the login user status, user information can be stored in
the variable and display can be changed by using JSTL tag library etc.
In the example above, it can also be displayed as described below. Since scope attribute is omitted in this
example, page scope is applied.
Spring Security offers various components for coordinating with Spring MVC. How to use the components for
coordinating with authentication process is explained.
<mvc:annotation-driven>
<mvc:argument-resolvers>
<!-- omitted -->
<!-- (1) -->
<bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalA
<!-- omitted -->
</mvc:argument-resolvers>
</mvc:annotation-driven>
Following method is created while receiving authentication information (UserDetails) by controller method.
@RequestMapping("account")
@Controller
public class AccountController {
The section describes customization points and extension methods offered by Spring Security.
Spring Security offers a lot of customization points. Since it is not possible to introduce all the customization
points here, we will focus on a few typical customization points here.
In Spring Security, the path for carrying out authentication process is “"/login"” by default, however it can be
changed by defining a bean as below.
<sec:http>
<sec:form-login login-processing-url="/authentication" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
(1) Specify a path for carrying out authentication process in login-processing-url attribute.
Note: When path of authentication process is changed, request destination of Login form must be changed as
well.
In Spring Security, request parameters for sending credentials (user name and password) are “username” and
“password” by default, however, these can be changed by defining a bean as shown below.
<sec:http>
<sec:form-login
username-parameter="uid"
password-parameter="pwd" /> <!-- (1) (2) -->
<!-- omitted -->
</sec:http>
(1) Specify a request parameter name for user name in username-parameter attribute.
Note: When request parameter name is changed, field name in Login form must also be changed.
Transition destination (default URL) after displaying login form on its own and by carrying out authentication
process is the root path of Web application ("/"), however it can be changed by defining a bean as given below.
<sec:http>
<sec:form-login default-target-url="/menu" /> <!-- (1) -->
</sec:http>
In default operation of Spring Security, when a request for a page for which authentication is required is received
during unauthenticated stage, the request thus received is temporarily stored in HTTP session and then transition
is done to authentication page. Although request is restored and redirected when the authentication is successful,
transition to the same screen can be assured by defining a bean as below.
<sec:http>
<sec:form-login
default-target-url="/menu"
always-use-default-target="true" /> <!-- (1) -->
</sec:http>
Applying AuthenticationSuccessHandler
When the requirements are not met only by using the system wherein default operations provided by Spring Se-
curity are customised, implementation class of AuthenticationSuccessHandler interface can be applied
directly by defining a bean as given below.
<sec:http>
<sec:form-login authentication-success-handler-ref="authenticationSuccessHandler" /> <!-- (2)
</sec:http>
Customization points for the response at the time of authentication failure are explained.
In default operation of Spring Security, the user is redirected to a URL wherein a query parameter "error"is
assigned to path for displaying login form, however it can be changed by defining a bean as given below.
<sec:http>
<sec:form-login authentication-failure-url="/loginFailure" /> <!-- (1) -->
</sec:http>
Sr.No. Description
Applying AuthenticationFailureHandler
When the requirements are not met only by using the system wherein default operations provided by Spring Se-
curity are customised, implementation class of AuthenticationFailureHandler interface can be applied
directly by defining a bean as below.
<sec:http>
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler" /> <!-- (7)
</sec:http>
(4) BadCredentialsException
It is thrown at the time of authentication error due to password verification failure.
(5) UsernameNotFoundException
It is thrown at the time of authentication error due to invalid user ID (non-existent user ID).
When
org.springframework.security.authentication.dao.AbstractUserDetailsAut
specifies inherited class in
the authentication provider, the exception changes to BadCredentialsException if
hideUserNotFoundExceptions property is not changed to false.
(6) DisabledException
It is thrown at the time of authentication error due to invalid user ID.
1768
(7) Set authenticationFailureHandler in 9 Security countermeasures
authentication-failure-handler-ref attribute.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When an exception defined in exceptionMappings property occur, the user is redirected to transition desti-
nation mapped in the exception, however, since exception object thus occurred is not stored in the session scope,
error message generated by Spring Security cannot be displayed on the screen.
Therefore, error message displayed in the transition destination screen must be generated by the process for redi-
rect (Controller or View process).
Further, it must be added that since the processes referring properties below are not called, operation does not
undergo any change even after changing the setup value.
• useForward
• allowSessionCreation
In Spring Security, default path which carries out logout process is “"/logout"”, however it can be changed by
defining a bean as given below.
<sec:http>
<!-- omitted -->
<sec:logout logout-url="/auth/logout" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
(1) Set logout-url attribute and specify path to carry out logout process.
Note: When logout path is changed, request destination of logout form must be changed as well.
Tip: Behaviour during occurrence of system error When system error occurs, discontinuation of operations
is likely. If the operation is not to be continued after occurrence of system error, adopting following measures is
recommended.
An example wherein authentication information at the time of system exception occurrence is cleared using an
exception handling function of common library is explained. For details of exception handling function, refer
“Exception Handling”.
// (1)
public class LogoutSystemExceptionResolver extends SystemExceptionResolver {
// (2)
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse response, java.lang.Object handler,
java.lang.Exception ex) {
return resulut;
}
}
(1) Extend
org.terasoluna.gfw.web.exception.SystemExceptionResolver.SystemExceptionR
Further, the same requirement can also be met by clearing the session, besides using the method that clears
authentication information. Implement according to the project requirement.
Customization points for the response when logout process is successful is explained.
<sec:http>
<!-- omitted -->
<sec:logout logout-success-url="/logoutSuccess" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
(1) Set logout-success-url attribute and specify path for the transition when the logout is
successful.
Applying LogoutSuccessHandler
<sec:http>
<!-- omitted -->
<sec:logout success-handler-ref="logoutSuccessHandler" /> <!-- (2) -->
<!-- omitted -->
</sec:http>
When authentication fails, error message provided by Spring Security is displayed, however, the error message
can be changed.
When an unexpected error occurs (system error etc) during authentication process,
InternalAuthenticationServiceException exception is thrown. Since cause exception mes-
sage is set in the message retained by InternalAuthenticationServiceException, it should not be
displayed as it is on the screen.
For example, when a DB access error occurs while fetching user information from
database, an exception message retained by SQLException is displayed on the screen.
The measures like handling InternalAuthenticationServiceException by using
ExceptionMappingAuthenticationFailureHandler and transiting to the path for notifying
occurrence of system error are necessary for not displaying exception message of system error on the screen.
<bean id="authenticationFailureHandler"
class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailure
<property name="defaultFailureUrl" value="/login?error" />
<property name="exceptionMappings">
<props>
<prop key="org.springframework.security.authentication.InternalAuthenticationService
/login?systemError
</prop>
<!-- omitted -->
</props>
</property>
</bean>
<sec:http>
<sec:form-login authentication-failure-handler-ref="authenticationFailureHandler" />
</sec:http>
Query parameter (systemError) is used for identifying occurrence of system error and transition is made to
login form. When systemError is specified in the query parameter, in the login form specified in the transition
destination, a fixed error message is displayed instead of an authentication exception message.
<c:choose>
<c:when test="${param.containsKey('error')}">
<span style="color: red;">
<c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>
</span>
</c:when>
<c:when test="${param.containsKey('systemError')}">
<span style="color: red;">
System Error occurred.
</span>
</c:when>
</c:choose>
Note: An implementation example for transiting to login form is introduced, however it is also possible to move
to system error screen.
A check is carried out in advance for obvious input error on the authentication page during reduction of load on
DB server etc. In such a case, input check using Bean validation can also be carried out.
An example of input check using Bean Validation is explained below. For details of Bean Validation, refer Input
Validation.
// omitted
@NotEmpty // (1)
private String username;
@NotEmpty // (1)
private String password;
// omitted
@ModelAttribute
public LoginForm setupForm() { // (1)
return new LoginForm();
}
@RequestMapping(value = "login")
public String login(@Validated LoginForm form, BindingResult result) {
// omitted
if (result.hasErrors()) {
// omitted
}
return "forward:/authenticate"; // (2)
}
In addition, authentication path is added to Spring Security servlet filter for Spring Security processing even during
the transition using Forward.
<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>
<!-- (1) -->
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/authenticate</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Here, an expansion example for performing DB authentication by using 3 parameters - user name, password and
Company identifier (unique authentication parameter)is shown below.
Note: Since a unique parameter is added as an parameter for authentication in this example, implementation
class of Authenticationinterface and Authentication Filter class for generating Authentication must be
expanded.
When the authentication is to be performed only by user name and password, authentication process can be
expanded only by creating implementation class of AuthenticationProvider interface
// import omitted
public class CompanyIdUsernamePasswordAuthenticationToken extends
UsernamePasswordAuthenticationToken {
// (1)
private final String companyId;
// (2)
public CompanyIdUsernamePasswordAuthenticationToken(
Object principal, Object credentials, String companyId) {
super(principal, credentials);
this.companyId = companyId;
}
// (3)
public CompanyIdUsernamePasswordAuthenticationToken(
Object principal, Object credentials, String companyId,
Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
this.companyId = companyId;
}
(2) Create a constructor to be used while creating an instance which retains information prior to
authentication (information specified by request parameter).
(3) Create a constructor to be used while creating an instance which retains authenticated information.
Authenticated state is reached by passing authorization information to the argument of parent class
constructor.
// import omitted
public class CompanyIdUsernamePasswordAuthenticationProvider extends
DaoAuthenticationProvider {
// omitted
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// (1)
super.additionalAuthenticationChecks(userDetails, authentication);
// (2)
CompanyIdUsernamePasswordAuthenticationToken companyIdUsernamePasswordAuthentication =
(CompanyIdUsernamePasswordAuthenticationToken) authentication;
String requestedCompanyId = companyIdUsernamePasswordAuthentication.getCompanyId();
String companyId = ((SampleUserDetails) userDetails).getAccount().getCompanyId();
if (!companyId.equals(requestedCompanyId)) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
String companyId = ((SampleUserDetails) user).getAccount()
.getCompanyId();
// (3)
return new CompanyIdUsernamePasswordAuthenticationToken(user,
authentication.getCredentials(), companyId,
user.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
// (4)
return CompanyIdUsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
(1) Call parent class method and implement check process provided by Spring Security.
This process also includes password authentication process.
(2) When password authentication is successful, check validity of company identifier (unique
authentication parameter).
In the example above, it is checked whether the requested company identifier and the company
identifier retained in the table are identical.
(3) When password authentication and unique authentication process are successful,
CompanyIdUsernamePasswordAuthenticationTokenis created in an authenticated state
and then returned.
Note: User existence check, user status check (checks for invalid user, locked user, expired user etc) are carried
out as processes of parent class before calling additionalAuthenticationChecks method.
// import omitted
public class CompanyIdUsernamePasswordAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: "
+ request.getMethod());
}
// (1)
// Obtain UserName, Password, CompanyId
String username = super.obtainUsername(request);
String password = super.obtainPassword(request);
String companyId = obtainCompanyId(request);
if (username == null) {
username = "";
} else {
username = username.trim();
}
if (password == null) {
password = "";
}
CompanyIdUsernamePasswordAuthenticationToken authRequest =
new CompanyIdUsernamePasswordAuthenticationToken(username, password, companyId);
// (3)
protected String obtainCompanyId(HttpServletRequest request) {
return request.getParameter("companyId");
}
}
Company identifier is added for login form (JSP) created by Creating login form.
DB authentication process which use user name, password and company identifier (unique authentication param-
eter) are applied in Spring Security.
<sec:logout
logout-url="/logout"
logout-success-url="/login" />
</sec:http>
</bean>
<bean id="csrfTokenRepository"
class="org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository" />
Here, a bean of
org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPo
class used while specifying <sec:form-login> tag is specified.
(5) Specify RequestMatcher instance for detecting a request performing authentication process, in
requiresAuthenticationRequestMatcher property.
When Basic authentication process and logout process are to be enabled by specifying
auto-config="false" or by omitting specification, <sec:http-basic> tag and <sec:logout> tag
must be defined explicitly.
Based on security requirements, it is likely that the implementation is not possible using the class which imple-
ments PasswordEncoder described earlier. In particular, when it is necessary to follow hashing requirements
used in the existing account information, it may not meet the requirements of PasswordEncoder described
earlier.
• Algorithm is SHA-512.
• Salt is stored in the account table column and it must be passed from outside of PasswordEncoder.
Warning: In Spring Security 3.1.4 and earlier versions, a class that implements
org.springframework.security.authentication.encoding.PasswordEncoderwas
used in the hashing, however, it is deprecated in the 3.1.4 and subsequent versions.
Using ShaPasswordEncoder
This guideline explains the use of PasswordEncoderof deprecated package using an example of
ShaPasswordEncoder.
When the hashing requirements are as below, requirements can be met by using ShaPasswordEncoder.
• Algorithm is SHA-512
<bean id ="passwordEncoder"
class="org.springframework.security.authentication.encoding.ShaPasswordEncoder"> <!-- (1) -->
<constructor-arg value="512" /> <!-- (2) -->
<property name="iterations" value="1000" /> <!-- (3) -->
</bean>
<bean id="authenticationProvider"
class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<!-- omitted -->
<bean id="saltSource"
class="org.springframework.security.authentication.dao.ReflectionSaltSource"> <!-- (3) -->
<property name="userPropertyToUse" value="username" /> <!-- (4) -->
</bean>
@Inject
PasswordEncoder passwordEncoder;
9.2.4 Appendix
A method wherein a request is received by Spring MVC and login form is displayed is explained.
@Controller
@RequestMapping("/login")
public class LoginController { // (1)
@RequestMapping
public String index() {
return "login";
}
}
As per this example, it is also possible to substitute by using <mvc:view-controller> in case of a controller
with only one method which simply returns only the view name.
“Remember Me authentication” is one of the functions to enhance the user experience who frequently access
Websites, and retains logged in state of a user for a little longer than normal lifecycle. If this function is used, the
user can login again without entering user name and password by using a Token for Remember Me authentication
retained by a Cookie, even when the browser is closed or session has timed-out. Note that, this function is enabled
only when the user has authorised retaining the logged in state.
Spring Security supports “Remember Me authentication of Hash-Based Token method and Persistent Token
method. Hash-Based Token method is used as a default.
<sec:http>
<!-- omitted -->
<sec:remember-me key="terasoluna-tourreservation-km/ylnHv"
token-validity-seconds="#{30 * 24 * 60 * 60}" /> <!-- (1) (2) -->
<!-- omitted -->
</sec:http>
(1) Specify a key value in key attribute for identifying an application that generates a Token for
Remember Me authentication.
When key value is not specified, a unique value is generated every time an application starts.
Note that, when a key value retained by Hash-Based Token and a key value retained by server vary, it
is handled as an invalid Token.
In other words, when a Hash-Based Token generated before restarting an application is to be handled
as a valid Token, key attribute must be specified.
(2) Specify validity period of Token for Remember Me authentication in seconds, in the
token-validity-seconds attribute.
When it is not specified, the validity period is 14 days by default.
In the example above, 30 days has been set as a validity period.
For attributes other than above, refer Spring Security Reference -The Security Namespace (<remember-me>) -.
Default value of following settings is changed from Spring Security 4.0 version
• remember-me-parameter
• remember-me-cookie
A flag (checkbox field) to specify use of “Remember Me authentication” function is provided in the login form.
(1) Add a flag (checkbox field) for specifying whether “Remember Me authentication” function is used
and specify remember_me in the field name (request parameter name).
If authentication process is carried out after ticking the checkbox, “Remember Me authentication”
function is applied for subsequent requests.
9.3 Authorization
9.3.1 Overview
Authorization process controls the resources that can be accessed by the users of the application. The standard
method to control the resources that can be accessed by the user include defining an access policy for each resource
(or a group of resources) and control the resources by checking the access policy when the user tries to access a
resource.
Access policy defines whether to allow a user to access a particular resource. An access policy for following 3
types of resources is defined in Spring Security.
• Web resource
• Java method
• Domain object *1
This section introduces an implementation example (definition example) wherein authorization process is applied
to access “Web resource”, “Java method” and “Screen fields of JSP” and explains the authorization function of
Spring Security.
*1 For authorization function to access domain object , refer Spring Security Reference -Domain Object Security (ACLs)-.
(4) FilterSecurityInterceptor accesses the resource only if the access rights have been
granted by AccessDecisionManager.
ExceptionTranslationFilter
FilterSecurityInterceptor
AccessDecisionManager
AccessDecisionManager interface checks whether the user has the access rights for the resource which he
is trying to access.
Spring Security provides 3 types of implementation classes and all the classes call AccessDecisionVoter
interface method and determine whether the access rights have been granted. AccessDecisionVoter votes
for “Assign”, “Deny” or Abstain” and then implementation class of AccessDecisionManager aggregates the
voting results and determines final access rights. AccessDeniedException exception is thrown and access
is denied if determined as “no access rights”.
Note that, if all the voting results indicate “Abstain”, it is determined as “no access rights” in Spring Security by
default.
AffirmativeBased An implementation class which assigns the access rights when 1 vote is given to
“Assign” during voting by AccessDecisionVoter.
Implementation class used as a default.
ConsensusBased An implementation class which assigns the access rights when the majority of votes
are for “Assign” during voting for all AccessDecisionVoter.
When 1 vote each is given to “Assign” and “Deny” or when it is a tie, it is considered
as “Have access rights” by default in Spring Security.
UnanimousBased An implementation class which does not give access rights when 1 vote is given to
“Deny” during voting by AccessDecisionVoter.
If only one AccessDecisionVoter is used, no difference is observed in the operation regardless of the imple-
mentation class. When multiple AccessDecisionVoter are used, implementation class should be selected in
accordance with the requirements.
AccessDecisionVoter
AccessDecisionVoter is an interface which checks the access policy specified in the resource which the
user is trying to access and votes for whether the access rights are to be granted.
WebExpressionVoter An implementation class which carries out voting by checking the rights information
retained by authentication information (Authentication) through SpEL and
request information (HttpServletRequest).
RoleVoter An implementation class which carries out voting by checking the role of the user.
RoleHierarchyVoter An implementation class which carries out voting by checking the role hierarchy of a
user.
AuthenticatedVoter An implementation class which carries out voting by checking the authentication
status.
A bean definition example (how to specify an access policy) and implementation method required for using au-
thorization function are explained.
Spring Security supports Spring Expression Language (SpEL) as a method which describes how to specify an
access policy. Although there are other methods which do not use SpEL, this guideline explains how to specify
an access policy by using Expression. How to use SpEL is briefly explained in this section, however for detailed
description, refer Spring Framework Reference Documentation -Spring Expression Language (SpEL)-.
Expression Description
hasRole(String role) Return true when logged in user has a role specified in the argument.
hasAnyRole(String... Return true when logged in user has one of the roles specified in the
roles) argument.
isAnonymous() Return true in case of an anonymous user who has not logged in.
isFullyAuthenticated() Return true in case of a user who has logged in using normal authentication
process instead of Remember Me authentication.
principal Return user information of authenticated user (an object of a class which
implements UserDetails interface).
Since user information and authentication information of a logged in user can be accessed when principal and
authentication are used as Expressions, an access policy can be set by using attributes other than role.
It was necessary to specify "ROLE_" prefix to role name till Spring Security 3.2. However, specifying "ROLE_"
prefix is no longer required from Spring Security 4.0 onwards.
Example)
Expression Description
Using operator
Determination using an operator can also be performed. In the example below, access can be granted if both role
and requested IP address match.
<sec:http>
<sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN') and hasIpAddress('192.168
Operator Description
[expression-1] Return true when both expression-1 and expression-2 are true.
and
[expression-2]
![expression] Return false when expression is true and return true when expression is false.
Spring Security performs authorization process for Web resource (HTTP request) using a servlet filter system.
Define a bean as below when authorization process is to be applied for a Web resource.
<sec:http>
<!-- omitted -->
<sec:intercept-url pattern="/**" access="isAuthenticated()" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
Since default value of use-expressions attribute of <sec:http> tag is changed to true from Spring
Security 4.0 onwards, it is no longer necessary to explicitly describe while using true.
How to define an access policy for a Web resource using a bean definition file is explained.
Specifying a Web resource for applying access policy First, a resource (HTTP request) for which an access
policy is to be applied, is specified. Attribute under <sec:intercept-url> tag is used for the specification
of a resource for which an access policy is to be applied.
Table.9.22 Attribute for specifying a resource for which an access policy is to be applied
pattern An attribute which uses a resource matching with path pattern specified in Ant format or
regular expression as an application target.
method An attribute which uses a resource as an application target when access is to be done by
using specified HTTP methods (GET, POST etc).
Specify “http” or “https”. An attribute for controlling the access by specified protocol.
requires-channel
if it is not specified, either of these can be accessed.
<sec:http >
<sec:intercept-url pattern="/admin/accounts/**" access="..."/>
<sec:intercept-url pattern="/admin/**" access="..."/>
<sec:intercept-url pattern="/**" access="..."/>
<!-- omitted -->
</sec:http>
Spring Security matches the requests in the defined order and the definition which is matched at first is applied.
Therefore, definition order must be taken into consideration even while specifying an access policy by using a
bean definition file.
Path pattern is interpreted in Ant format, in the default operation of Spring Security. When the path pattern
is to be specified in the regular expression, "regex" should be specified in request-matcher attribute of
<sec:http> tag
<sec:http request-matcher="regex">
<sec:intercept-url pattern="/admin/accounts/.*" access=hasRole('ACCOUNT_MANAGER')" />
<!-- omitted -->
</sec:http>
Specifying an access policy Next, an access policy is specified. Access policy is specified in access attribute
of <sec:intercept-url> tag.
<sec:http>
<sec:intercept-url pattern="/admin/accounts/**" access="hasRole('ACCOUNT_MANAGER')"/>
<sec:intercept-url pattern="/admin/configurations/**" access="hasIpAddress('127.0.0.1') a
<sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')" />
<!-- omitted -->
</sec:http>
access Specify access control expression using SpEL and a role that can be accessed.
A setting example with roles namely “ROLE_USER” and “ROLE_ADMIN” assigned to login user, is shown
below.
<sec:http>
<sec:intercept-url pattern="/reserve/**" access="hasAnyRole('USER','ADMIN')" /> <!-- (1)
<sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')" /> <!-- (2) -->
<sec:intercept-url pattern="/**" access="denyAll" /> <!-- (3) -->
<!-- omitted -->
</sec:http>
The request received from client is matched with the pattern in intercept-url, starting from the top and
access is granted for the matched pattern. Therefore, pattern should always be described from limited
patterns.
SpEL is enabled in Spring Security by default. SpEL described in access attribute is determined as a true value.
If the expression is true, access is granted. How to use is shown below.
<sec:http>
<sec:intercept-url pattern="/admin/**" access="hasRole('ADMIN')"/> <!-- (1) -->
<!-- omitted -->
</sec:http>
(1) Return true if the logged in user retains the specified role, by specifying hasRole(’Role
name’).
For main Expression that can be used, refer How to describe an access policy.
Spring Security performs authorization process for calling a method of Bean which is managed in DI container by
using Spring AOP system.
Authorization process for the method is provided by considering its use for calling a domain layer (service layer)
method. If authorization process for the method is used, a detailed access policy can be defined since a property
of domain object can be checked.
Enabling AOP
When the authorization process for the method is to be used, a component (AOP) for performing authorization
process for calling the method must be enabled. If AOP is enabled, access policy can be defined in the method
annotation.
• @Secured
This guideline explains how to use @PreAuthorize and @PostAuthorize which enable the use of access
policy by Expression.
(1) AOP which performs authorization process for method calling is enabled if
<sec:global-method-security> tag is assigned.
An annotation which specifies an access policy is used and an access policy is defined for each method by applying
authorization process for the method.
Specifying an access policy to be applied prior to method execution When an access policy is to be
specified for applying prior to method execution, @PreAuthorize is used.
When the results of the Expression specified in value attribute of @PreAuthorize become true, execution
of the method is allowed. In the example below, it has been defined that only administrator can access the account
information for other persons.
// (1) (2)
@PreAuthorize("hasRole('ADMIN') or (#username == principal.username)")
public Account findOne(String username) {
return accountRepository.findOne(username);
}
The part wherein a method argument is accessed from the Expression is discussed here. Specifically,
“#username” part accesses the argument. A method argument can be accessed by specifying Expression of
“# + argument name” format in Expression.
Spring Security resolves the argument name from debug information output in the
class. However an argument name can be explicitly specified by using an annotation
(@org.springframework.security.access.method.P)
A variable name can be specified explicitly by using the annotation if it is applicable to following cases.
• When it is to be accessed by using a name different from that of actual variable name in the Expression (for
example shortened name)
Note that when #username and a method argument - username name are identical, @P can be omitted. How-
ever, since Spring Security resolves the argument name by using an argument name of implementation class, note
that the argument name of implementation class should match with #username specified in @PreAuthorize
when @PreAuthorize annotation is defined in the interface.
When compile option added from JDK 8 (-parameters) is used, meta data for reflection is generated in the
method parameter. Hence argument name is resolved even when the annotation is not specified.
Specifying access policy to be applied after method execution When an access policy to be applied after
execution of the method is specified, @PostAuthorize is used.
When the results of the Expression specified in value attribute of @PostAuthorize become true, results of
method execution are returned to the call source. In the example below, it has been defined such that the user is
not allowed to access the information of the user belonging to other department.
@PreAuthorize("...")
@PostAuthorize("(returnObject == null) " +
"or (returnObject.departmentCode == principal.account.departmentCode)")
public Account findOne(String username) {
return accountRepository.findOne(username);
}
The part wherein the return value of the method is accessed from the Expression is discussed here. Specifically,
“returnObject.departmentCode” part accesses the return value. The return value of the method can be
accessed by specifying returnObject in the Expression.
Spring Security can apply authorization process for JSP screen fields by using a JSP tag library.
Here, a method to apply authorization process for accessing JSP screen fields is explained using a simple definition
as an example.
A condition to allow display (access policy) is defined in JSP while defining an access policy for JSP screen fields
using a JSP tag library.
(1) Enclose part for which an access policy is to be applied with <sec:authorize> tag.
(2) Define an access policy in access attribute. Here, an access policy - “allow display in case of
administrator” is defined.
When an access policy is to be defined for buttons or links (screen fields associated with the requests to the server),
it is linked with an access policy defined in the Web resource for request. url attribute of <sec:authorize>
tag is used for linking with access policy specified in the Web resource.
JSP process implemented in <sec:authorize> tag is executed only when Web resource specified in url
attribute can be accessed.
<ul>
<!-- (1) -->
<sec:authorize url="/admin/accounts"> <!-- (2) -->
<li>
<a href="<c:url value='/admin/accounts' />">Account Management</a>
</li>
</sec:authorize>
</ul>
(1) Enclose part which outputs button or link with <sec:authorize> tag.
(2) Specify a URL for accessing a Web resource in url attribute of <sec:authorize> tag.
Here, an access policy - “Allow display when a Web resource allocated with "/admin/accounts"
URL can be accessed” is defined however it is not necessary to be directly aware of the access policy
defined in Web resource.
When a different access policy is specified by HTTP method while defining an access policy of Web resource, the
definition to be linked must be identified by using method attribute of <sec:authorize> tag.
Determination results of authorization process called by using <sec:authorize> tag can be reused by storing
in the variable.
<sec:authorize url="/admin/accounts"
var="hasAccountsAuthority"/> <!-- (1) -->
(1) Specify a variable name for storing determination results in var attribute.
When access is allowed, true is set in the variable.
Spring Security handles the errors as shown in the following flow and controls the response when access to a
resource is denied.
AccessDeniedHandler
AccessDeniedHandler interface handles the error responses when the access is denied to authenticated users.
Spring Security offers following classes as the implementation class of AccessDeniedHandler interface.
Set 403 (Forbidden) in HTTP response code and move to specified error page.
AccessDeniedHandlerImpl
When error page is not specified, send error response
(HttpServletResponse#sendError) by setting 403 (Forbidden) in HTTP
response code.
In the default setting of Spring Security, AccessDeniedHandlerImplis used wherein an error page is not
specified.
AuthenticationEntryPoint
AuthenticationEntryPoint interface handles the error responses when the access is denied
to unauthenticated users. Spring Security offers following class as an implementation class of
AuthenticationEntryPoint interface.
Set 403 (Forbidden) in HTTP response code and send error response
Http403ForbiddenEntryPoint
(HttpServletResponse#sendError).
In the default setting of Spring Security, when the access is denied to the authenticated user, an error page of
application server is displayed. If error page of application server is displayed, it is likely to result in system
degradation. Hence it is recommended to display an appropriate error screen. Error page can be specified by
defining a bean as below.
<sec:http>
<!-- omitted -->
<sec:access-denied-handler
error-page="/WEB-INF/views/common/error/accessDeniedError.jsp" /> <!-- (1) -->
<!-- omitted -->
</sec:http>
Error page for authorization error can also be specified by using an error page function of servlet container.
When error page function of servlet container is used, error page is specified by using <error-page> tag of
web.xml.
<error-page>
<error-code>403</error-code>
<location>/WEB-INF/views/common/error/accessDeniedError.jsp</location>
</error-page>
This section explains customization points and extension methods offered by Spring Security.
Spring Security provides a lot of customization points of which only a few will be introduced here. This section
will focus on some typical customization points.
Here, a method to customise the operation when an access from authenticated user is denied, is explained.
Applying AccessDeniedHandler
When the requirements are not met only by customising default operation provided by Spring Security, imple-
mentation class of AccessDeniedHandler interface can be directly applied.
For example, when an authorization error occurs in Ajax request (REST API etc), error information is dis-
played in JSON format instead of displaying an error page (HTML). In such a case, an implementation class
of AccessDeniedHandler interface can be created and applied in Spring Security.
public JsonDelegatingAccessDeniedHandler(
RequestMatcher jsonRequestMatcher, AccessDeniedHandler delegateHandler) {
this.jsonRequestMatcher = jsonRequestMatcher;
this.delegateHandler = delegateHandler;
}
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp"/>
</bean>
</constructor-arg>
</bean>
<sec:http>
<!-- omitted -->
<sec:access-denied-handler ref="accessDeniedHandler" /> <!-- (2) -->
<!-- omitted -->
</sec:http>
Here, a method to customise operation when access is denied to an unauthenticated user, is explained.
Similarly to authenticated user, when authorization error occurs in Ajax request (REST API etc), error information
is displayed in JSON format instead of displaying it in login page (HTML). In such a case, implementation class
of AuthenticationEntryPoint interface for each pattern of request is applied in Spring Security.
<bean class="com.example.web.security.JsonAuthenticationEntryPoint"/>
</entry>
</map>
</constructor-arg>
<property name="defaultEntryPoint">
<bean class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPo
<constructor-arg value="/login"/>
</bean>
</property>
</bean>
(1) Define a bean for implementation class of AuthenticationEntryPoint interface and register
in DI container.
Here, an implementation class of AuthenticationEntryPoint interface is applied for each
pattern of request by using DelegatingAuthenticationEntryPoint class offered by Spring
Security.
Role hierarchy
Higher level roles can also access the resources for which access is granted to the lower level roles. When the role
For example, when a hierarchy relation is established wherein “ROLE_ADMIN” is a higher role and
“ROLE_USER” is a lower role, and if an access policy is set as below, user with “ROLE_ADMIN” rights can
access a path under "/user"(a path which can be accessed by a user with “ROLE_USER” rights).
<sec:http>
<sec:intercept-url pattern="/user/**" access="hasAnyRole('USER')" />
<!-- omitted -->
</sec:http>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl"> <!-- (1) -->
<property name="hierarchy"> <!-- (2) -->
<value>
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
</value>
</property>
</bean>
(1) Specify
org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
class.
RoleHierarchyImpl is a default implementation class offered by Spring Security.
A method to apply role hierarchy to authorization process for Web resource and JSP screen fields is explained.
<sec:http>
<!-- omitted -->
<sec:expression-handler ref="webExpressionHandler" /> <!-- (3) -->
</sec:http>
A method to apply role hierarchy to authorization process for Java method is explained.
<bean id="methodExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHa
<property name="roleHierarchy" ref="roleHierarchy"/> <!-- (2) -->
</bean>
<sec:global-method-security pre-post-annotations="enabled">
<sec:expression-handler ref="methodExpressionHandler" /> <!-- (3) -->
</sec:global-method-security>
9.4.1 Overview
This chapter explains the “Security measures required for session management in Web applications” and “Session
related functions provided by Spring Security”.
Generally, countermeasures are required for the following attacks for session management in Web application.
Countermeasure Description
Session hijacking attack An attack wherein the session ID is stolen using communication
eavesdropping, analogy from the regularity and cross-site scripting and using
the system with stolen ID by pretending to be the user.
Session fixation attack The attacker allows the other user to log in to the system by using the
previously issued session ID. The attacker uses the system by pretending to be
the logged in user.
Countermeasures for session hijacking attacks enable you to only prevent the session ID from being stolen. If it is
stolen, application server cannot determine whether the request is from an authorized user or from the attacker.
Following countermeasures are necessary to protect the application from such session hijacking attacks.
Countermeasures Description
Create a session ID that is Use a random value for Session ID that is difficult to presume (Secure) instead
difficult to presume. of a value such as sequential number that can be easily cracked.
Basically, the mechanism provided by the application server to create session
ID should be used.
Encrypt communication using Encrypt the communication with HTTPS protocol that includes exchange of
HTTPS information which can cause a lot of trouble when stolen.
Since communication eavesdropping can be done easily by using a free
software etc., it is important to keep it encrypted to prevent it from being
decrypted even if it is eavesdropped.
Link Session ID using a Cookie While linking the session ID between client and server, set it to be linked by
using a Cookie and disable the URL Rewriting function.
Specify HttpOnly attribute of If you specify ‘‘ HttpOnly‘‘ attribute of Cookie, session ID cannot be stolen
Cookie using cross site scripting since Cookie cannot be accessed from JavaScript.
Specify Secure attribute in If you specify ‘‘ Secure‘‘ attribute in Cookie, the risk of session ID getting
Cookie stolen when you accidentally use HTTP communication is reduced since
Cookie is sent to the server only during HTTPS communication.
URL Rewriting is a mechanism to retain the session with the client that cannot use Cookie. Particularly, the
Session ID between client and server is linked by including Session ID as a request parameter of the URL.
http://localhost:8080/;jsessionid=7E6EDE4D3317FC5F14FD912BEAC96646
called. These methods are also called in the JSP tag library provided by JSTL and Spring.
• HttpServletResponse#encodeURL(String)
• HttpServletResponse#encodeRedirectURL(String)
If URL Rewriting is done, session ID in the URL is exposed resulting in higher risk of session ID being stolen.
Therefore, it is recommended to disable the URL Rewriting function of Servlet container while supporting only
the clients that can use the Cookie.
Following countermeasures are necessary to protect the application from Session fixation attack.
Countermeasure Description
Disable URL Rewriting function. If URL Rewriting function is disabled, session ID issued previously cannot be
used by the attacker and a new session is started.
Change session ID after login By changing the session ID after login, the session ID issued previously cannot
be used by the attacker.
Function Description
Security measures Countermeasures for attacks using session ID of session hijacking attacks.
Lifecycle control Function to control the lifecycle of the session from generation to discard of a
session.
Multiple login control Function to control a session if the same user logs in for multiple times.
The method to disable a URL rewriting function and link session ID using a Cookie is explained.
Spring Security provides a mechanism to disable URL Rewriting and this function is applied by default. When it
is necessary to support the clients who cannot use a Cookie, a Bean is defined so as to authorize URL rewriting.
(1) In Spring Security, since the value of ‘‘ disable-url-rewriting‘‘ is ‘‘ true‘‘ by default, URL Rewriting
is not performed.
Set false in disable-url-rewriting attribute of <sec:http> element to enable URL
Rewriting.
<session-config>
<cookie-config>
<http-only>true</http-only> <!-- (1) -->
</cookie-config>
<tracking-mode>COOKIE</tracking-mode> <!-- (2) -->
</session-config>
(1) Specify true in <http-only> element while assigning HttpOnly attribute in Cookie.
Default value is set to ‘‘ true‘‘ based on the application server used.
(3) Specify COOKIE in <tracking-mode> element to disable the URL Rewriting function.
Although it is omitted from the definition example mentioned above, Secure attribute can be assigned for the
Cookie by adding <secure>true</secure> in <cookie-config>. However, to secure the cookie, the
method wherein a middleware (SSL accelerators and Web server etc.) that performs HTTPS communication with
the client is assigned, is used instead of specifying in ‘‘ web.xml‘‘ .
In actual system development, HTTPS is used very rarely in the local development environment. Also, even in
the production environment, HTTPS is used for SSL accelerators and communication with Web server and there
are many cases where communication with application server is carried out using HTTP. If ‘‘ Secure‘‘ attribute
is specified in ‘‘ web.xml‘‘ under such environment, web.xml and web-fragment.xml will be provided for
each execution environment. It is not recommended since file management becomes complicated.
A method to use session management function of Spring Security is explained. Define a bean as shown below to
use the session management function process of Spring Security.
<sec:http>
<!-- omitted -->
<sec:session-management /> <!-- (1) -->
<!-- omitted -->
</sec:http>
Spring Security provides the following four options to change the session ID when login is successful, as the
countermeasures against session fixation attack.
Options Description
migrateSession Discard the session that was used before login and create a new session.
The objects stored in the session before login are transferred to the new session
when this option is used.
(This is the default operation in Servlet 3.0 container and earlier versions)
newSession This option changes the session ID in the same way as ‘‘ migrateSession‘‘ ,
however, the objects stored before login are not transferred to the new session.
<sec:session-management
session-fixation-protection="newSession"/> <!-- (1) -->
Spring Security uses HTTP session for sharing objects such as authentication information across requests. The
lifecycle of the session (Generating and discarding a session) is controlled in the Spring Security process.
HTTP session is used in the default implementation provided by Spring Security, however, the architecture also
enables storing the objects in other than HTTP session (Database and key-value store etc.).
Generating a session
The guidelines by which a session can be generated and used in the Spring Security process can be selected from
the following options.
Option Description
always Generate a new session when the session does not exist.
If this option is specified, the session is generated even though it is not used in the
Spring Security process.
ifRequired Generate and use a new session at the time of storing object in the session when the
session does not exist.(Default operation)
never If the session does not exist, session is not generated and used.
However, use the session if it already exists.
(1) Specify creation guidelines for the session to be changed in create-session attribute of
<sec:http> element.
Discarding a session
When an object is to be stored in the session, it is a common practice to ensure that session of the user who is idle
for a certain period of time is automatically discarded by specifying an appropriate session time-out value.
Specify session timeout for Servlet container. In some cases, the specification method independent of server may
be provided depending on the application server, however, the specification methods stated in Servlet standard
specifications are described here.
<session-config>
<session-timeout>60</session-timeout> <!-- (1) -->
<!-- omitted -->
</session-config>
Spring Security provides a function to detect a request with an invalid session. Most of the requests handled as
invalid sessions are requests after session timeout. By default, this functionality is disabled, however, it can be
enabled by defining a bean as shown below.
<sec:session-management
invalid-session-url="/error/invalidSession"/>
(1) Specify the path for redirect destination when a request with invalid session is detected in
invalid-session-url attribute of <sec:session-management> element.
If the function to detect a request with invalid session is enabled, all the requests that pass through Servlet filter of
Spring Security are checked. Therefore, check is performed even for pages that can be accessed when session is
in invalid state.
This operation can be changed by defining a separate bean for the path to be excluded from the check target. For
example, a bean is defined as shown below to specify the path to open the top page ("/") in the exclusion path.
(2) Specify the path pattern to apply SecurityFilterChain created using <sec:http> element
of (1).
Ant-style path notation and regular expression are the two formats that can be specified for the path
pattern. By default, it is handled as Ant-style path pattern.
Note that, it is also possible to directly specify ‘‘ RequestMatcher‘‘ object without the path pattern.
Spring Security provides a function to control multiple logins using the same user name (login ID). By default this
function is disabled. However, it can be enabled by using Enabling session lifecycle detection.
This section introduces a method to use default implementation of Spring Security. HTTP session is used in the
default implementation provided by Spring Security, however, the architecture also enables storing objects in other
than HTTP session (Database or key-value store etc.). However, please note that the method introduced here is
the Implementation method with the constraints of the Warning mentioned above, during the application.
Todo
The information about the implementation method that does not use in-memory will be added later.
The function to control multiple login manages the session state for each user by using session lifecycle (Gen-
erating and discarding session) detection mechanism. Therefore, while using multiple login control function,
HttpSessionEventPublisher class provided by Spring Security must be registered in Servlet Container.
<listener>
<!-- (1) -->
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
A bean is defined as shown below if multiple login is to be prevented by generating an authentication error in case
of the users who have already logged in with the same user name (login ID).
<sec:session-management>
<sec:concurrency-control
max-sessions="1"
error-if-maximum-exceeded="true"/> <!-- (1) (2) -->
</sec:session-management>
In case of the users who have already logged in with the same user name (login ID), a bean is defined as below if
multiple login is to be prevented by invalidating the users who are already logged in.
<sec:session-management>
<sec:concurrency-control
max-sessions="1"
error-if-maximum-exceeded="false"
expired-url="/error/expire"/> <!-- (1) (2) -->
</sec:session-management>
(1) Specify the operation to be performed when the number of sessions a user can login concurrently has
exceeded, in error-if-maximum-exceeded attribute of <sec:concurrency-control>
element.
Specify ‘‘ false‘‘ if a new logged-in user is handled as a valid user.
(2) Specify the path for redirect destination when a request from an invalidated user is detected in
expired-url attribute of <sec:concurrency-control> element.
This is because definition sequence of <sec:http> element is the priority sequence of
SecurityFilterChain.
9.5.1 Overview
This chapter explains Cross site request forgeries (hereafter referred to as CSRF) countermeasures function offered
by Spring Security.
CSRF is an attack that forces a user to perform unwanted actions on a different website in which the user is logged
in. by implementing a script in the Website or by automatic transfer (HTTP redirect).
• Re-entering password
• ‘Referer’ check
CSRF countermeasures function handles the malicious request sent from the Web page provided by the attacker
as an invalid request. Following methods can be used to attack if CSRF countermeasures are not implemented in
a Web application.
• User logs in to the Web application for which CSRF countermeasures are not implemented.
• User opens the Web page provided by the attacker under the skilful guidance of the attacker.
• The Web page prepared by the attacker sends a forged request to the Web application for which CSRF
countermeasures have not been implemented, using techniques such as automatic submission of form.
• Web applications for which CSRF countermeasures are not implemented handle forged request by the
attacker as a legitimate request.
CSRF countermeasures should be implemented not only for the login request but also for the login process. If
CSRF countermeasures are not implemented for login process, user is forced to login using the account created by
the attacker even before the user realizes it and there is a possibility of the operation history of login being stolen.
*1 OWASP is an abbreviation of Open Web Application Security Project. It is a not-for-profit international organization dedicated to
enable organizations to develop and maintain applications that can be trusted. It advocates measures such as effective approach etc.
with respect to security. https://www.owasp.org/index.php/Main_Page
Warning: CSRF measures at the time of the multi-part request (file upload)
About CSRF measures during file upload, file upload Servlet Filter settingshould be followed.
Spring Security issues a fixed token value (CSRF token) generated randomly for each session and sends the issued
CSRF token as a request parameter (hidden field in the HTML form). Accordingly, a system to determine whether
the request is from a normal Web page or from the Web page created by the attacker, is adopted.
(1) Client accesses the application server using the HTTP GET method.
(2) Spring Security generates a CSRF token and stores it in HTTP session.
The generated CSRF token links with the client using the hidden tag of HTML form.
(3) The client sends a request to the application server by clicking a button on the HTML form.
Since the CSRF token is embedded in a hidden field in the HTML form, CSRF token value is sent as
a request parameter.
(4) Spring Security checks if the CSRF token value specified in the request parameter and the CSRF
token value retained in the HTTP session are same when it is accessed using HTTP POST method.
If the token value does not match, an error is thrown as an invalid request (request from the attacker).
(5) Client accesses the application server using the HTTP GET method.
(6) Spring Security does not check the CSRF token value when it is accessed using GET method.
Since Spring Security can set the CSRF token value in request header, it is possible to implement CSRF counter-
measures for the requests for Ajax etc.
In the default implementation of Spring Security, check CSRF token for the request that uses the following HTTP
methods.
• POST
• PUT
• DELETE
• PATCH
GET, HEAD, OPTIONS, TRACE methods are not checked as these methods are not used to implement the request
to change the application status.
By using the RequestDataValueProcessorimplementation class for CSRF token, the token can be auto-
matically inserted as ‘hidden’ field using <form:form>tag of Spring tag library.
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor"> <!-- (1) -->
<constructor-arg>
<util:list>
<bean
class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueP
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValuePr
</util:list>
</constructor-arg>
</bean>
CSRF countermeasures function is enabled as a default by the settings described above from Spring Security 4.0
and subsequent versions. Therefore, CSRF countermeasures function should be disabled explicitly if you do not
<sec:http>
<!-- omitted -->
<sec:csrf disabled="true"/> <!-- Disabled by setting true in the 'disabled' attribute -->
<!-- omitted -->
</sec:http>
Spring Security provides the following two methods to link the CSRF token value between the client and server.
• Output the CSRF token value as a hidden field in the HTML form and link as a request parameter
• Output CSRF token information as HTML meta tag and link by setting the token value in the request header
at the time of Ajax communication
Spring Security has provided several components to coordinate with Spring MVC. How to use the component to
coordinate with CSRF countermeasures function is described below.
Auto output of hidden fields Implement the following JSP while creating a HTML form.
(1) Use <form:form> element provided by Spring MVC while creating an HTML form.
HTML form shown below is created when <form:form> element provided by Spring MVC is used.
<input type="hidden"
name="_csrf" value="63845086-6b57-4261-8440-97a3c6fa6b99" />
</div>
</form>
For example, when GET method is specified in method attribute as shown below, CSRF token inserted <input
type="hidden"> tag is not output.
The unique token can also be included in the URL itself, or a URL parameter. However, such placement
runs a greater risk that the URL will be exposed to an attacker, thus compromising the secret token.
CSRF token value can also be linked using HTML form without using Linkage with Spring MVC. If you want to
send a request using HTML form, output the CSRF token value as a hidden field in the HTML form and coordinate
as a request parameter.
The hidden fields are output as shown below if <sec:csrfInput> element provided by Spring Security is
specified. CSRF token value is linked as a request parameter by displaying the hidden fields in the HTML form.
By default, the name of the request parameter to link CSRF token value is _csrf .
If you want to send a request using Ajax, output the CSRF token information as HTML meta tag, and link by
setting the token value fetched from meta tag in the request header at the time of Ajax communication.
First, output the CSRF token information in HTML meta tag by using the JSP tag library provided by Spring
Security.
<head>
<!-- omitted -->
<sec:csrfMetaTags /> <!-- (1) -->
<!-- omitted -->
</head>
If <sec:csrfMetaTags> element is specified, the meta tags are output as shown below. By default, the name
of the request header to link CSRF token value is X-CSRF-TOKEN.
<head>
<!-- omitted -->
<meta name="_csrf_parameter" content="_csrf" />
<meta name="_csrf_header" content="X-CSRF-TOKEN" /> <!-- Header name -->
<meta name="_csrf"
content="63845086-6b57-4261-8440-97a3c6fa6b99" /> <!-- Token value -->
<!-- omitted -->
</head>
Then, fetch the CSRF token information from meta tag using JavaScript and set the CSRF token value in the
request header at the time of Ajax communication. (Implementation example using jQuery is described here)
$(function () {
var headerName = $("meta[name='_csrf_header']").attr("content"); // (1)
var tokenValue = $("meta[name='_csrf']").attr("content"); // (2)
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(headerName, tokenValue); // (3)
});
});
(1) Fetch the request header name for coordinating with the CSRF token value.
In order to control the transition destination in case of token check error, handle AccessDeniedException
which is an exception that occurs in CSRF token check error and specify the transition destination corresponding
to that exception.
Exception that occurs at the time of CSRF token check error is as follows.
InvalidCsrfTokenException Exception class to be used when the token value sent by the client does
not match with the token value stored at server side (Mainly invalid
request).
MissingCsrfTokenException Exception class to be used when the token value is not stored at server
side (Mainly session timeout).
It is possible to set the transition destination for each exception by handling the exception men-
tioned above using DelegatingAccessDeniedHandler class and assigning implementation class of
AccessDeniedHandler interface respectively.
Define a Bean as shown below if you want to transit to an exclusive error page (JSP) at the time of CSRF token
check error. (The following definition example is an excerpt from a blank project)
<sec:http>
<!-- omitted -->
<sec:access-denied-handler ref="accessDeniedHandler"/> <!-- (1) -->
<!-- omitted -->
</sec:http>
<bean id="accessDeniedHandler"
class="org.springframework.security.web.access.DelegatingAccessDeniedHandler"> <!-- (2) -->
<constructor-arg index="0"> <!-- (3) -->
<map>
<!-- (4) -->
<entry
key="org.springframework.security.web.csrf.InvalidCsrfTokenException">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/invalidCsrfTokenError.jsp" />
</bean>
</entry>
<!-- (5) -->
<entry
key="org.springframework.security.web.csrf.MissingCsrfTokenException">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/missingCsrfTokenError.jsp" />
</bean>
</entry>
</map>
</constructor-arg>
<!-- (6) -->
<constructor-arg index="1">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp" />
</bean>
</constructor-arg>
</bean>
(1) Specify the Bean name for AccessDeniedHandler to control each Exception in ref attribute of
<sec:access-denied-handler> tag.
If all the transition destinations in case of an error are on the same screen, the transition destination
should be specified in error-page attribute.
Refer to Transition destination at the time of authorization error when the exceptions are not handled
by <sec:access-denied-handler>.
(3) Using the first argument of constructor, define the exception for which transition destination is to be
specified separately (AccessDeniedException subclass) and the corresponding exception
handler (AccessDeniedHandler implementation class) in Map format.
(6) Specify the exception handler (AccessDeniedHandler implementation class) at the time of
default exception (subclass of AccessDeniedException not specified in (4)(5)) and the
transition destination using second argument of constructor.
When “Detection of request with invalid session” process of session management function is enabled, implemen-
tation class of AccessDeniedHandler interface to be linked with “Detection of request with invalid session”
process is used for MissingCsrfTokenException.
If status code other than 403 is to be returned when the CSRF token in request does
not match, it is necessary to create an independent AccessDeniedHandler which implements
org.springframework.security.web.access.AccessDeniedHandler interface.
9.6.1 Overview
This chapter explains how to coordinate with the security countermeasure function provided by browser.
Main Web browser provides a few security countermeasure functions so that the functions provided by the browser
are not affected. Some security countermeasure functions provided by the browser can control the operations by
displaying response header of HTTP at the server side.
Spring Security provides a system to enhance security of Web application by offering function to output the
security response header.
Even if the security response header is displayed, it does not guarantee 100% elimination of security risk. Ulti-
mately, user should consider it as a support function to reduce the security risk.
Note that, the support status of security header varies depending on the browser.
HTTP header may be overwritten by the application even though the following settings are done.
• X-Frame-Options
• X-Content-Type-Options
• X-XSS-Protection
• Strict-Transport-Security
Some browsers do not support handling these headers. Refer official site of the browser or the following pages.
• https://www.owasp.org/index.php/HTTP_Strict_Transport_Security (Strict-Transport-Security)
• https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet (X-Frame-Options)
Cache-Control
Cache-Control header indicates a method to cache the contents. Risk of unauthorized users viewing the protected
contents can be reduced by disabling caching for the protected contents of the browser..
Cache-Control header is overwritten when Controller class of Spring MVC defines form class of
@SessionAttributes or uses Model of @SessionAttributes attribute in the request handler.
Pragma header and Expires header are also output to enable Spring Security support the browser compatible with
HTTP1.0 as well.
X-Frame-Options
X-Frame-Options header indicates whether the contents within the frame (<frame> or <iframe> element) are
authorized. Risk of confidential information being stolen by using the malicious practice called Clickjacking can
be eliminated by disabling the display of contents within a frame.
The following header is output to deny the display within the frame.
X-Frame-Options: DENY
Note that, options other than the output example can be specified in X-Frame-Options header.
X-Content-Type-Options
X-Content-Type-Options header indicates a method to determine the contents type. Some browsers ignore the
value of Content-Type header and determine the contents by looking at the content details. Risk of attack using
cross-site scripting can be reduced when you disable contents view while determining the contents type.
The following header is output in order to disable viewing the content details while determining the type of
contents.
X-Content-Type-Options: nosniff
X-XSS-Protection
X-XSS-Protection header indicates the method to detect harmful script using XSS filter function of the browser.
Risk of attack using cross-site scripting can be reduced by enabling the XSS filter function and detecting harmful
script.
Following header is output to enable XSS filter function and detect harmful script.
X-XSS-Protection: 1; mode=block
Further, options other than the output example can be specified in X-XSS-Protection header.
Strict-Transport-Security
Strict-Transport-Security header indicates that user should access the browser by replacing HTTP with HTTPS
when user accesses the browser using HTTPS and then tries to access it using HTTP. Risk of user being directed to
malicious sites using malicious technique called as Man-in-the-Middle attack can be reduced by disabling HTTP
use after accessing the browser using HTTPS.
Following header is output to disable the use of HTTP after accessing browser using HTTPS.
Note: Strict-Transport-Security
Strict-Transport-Security header is output only when the application server is accessed using HTTPS in the default
implementation of Spring Security. Note that, Strict-Transport-Security header value can be changed by specifying
the option.
A method is executed to apply the security header output function described earlier.
Security header output function is added by Spring 3.2 and applied by default from Spring Security 4.0. Therefore,
a specific definition is not required to enable the security header output function. Further, when the security header
output function is not to be applied, it must be disabled explicitly.
Define a bean as given below when the security header output function is to be disabled.
<sec:http>
<!-- omitted -->
<sec:headers disabled="true"/> <!-- Disable by setting true in "disabled" attribute -->
<!-- omitted -->
</sec:http>
Define a bean as given below for selecting the security header to be output. Here, the example denotes output of
all security headers provided by Spring Security, but only required headers should be specified in practice.
(1) First disable the registration of the component which outputs the header applied by default.
Further, a method is also provided which disables security headers which are not required.
<sec:headers>
<sec:cache-control disabled="true"/> <!-- Disable by setting true in "disabled" attribute -->
</sec:headers>
Contents which are output by Spring Security by default, can be changed in the following header.
• X-Frame-Options
• X-XSS-Protection
• Strict-Transport-Security
An option *1 can be specified in the attribute of each element by changing the bean definition of Spring Security.
Spring Security can also output the headers which are not provided by default.
<sec:headers>
<sec:header name="X-WebKit-CSP" value="default-src 'self'"/>
</sec:headers>
(1) Add <sec:header> as child element of <sec:headers> element and specify the header name
in name attribute and header value in value attribute.
Spring Security can control the output of security header for each request pattern by using RequestMatcher
interface system.
For example, when the contents to be protected are stored under the path /secure/ and Cache-Control header
is to be output only when the contents to be protected are accessed, define a bean as follows.
class="org.springframework.security.web.header.writers.DelegatingRequestMatcherHeaderWriter"
<constructor-arg>
<bean class="org.springframework.security.web.util.matcher.AntPathRequestMatcher">
<constructor-arg value="/secure/**"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.web.header.writers.CacheControlHeadersWriter"/>
</constructor-arg>
</bean>
<sec:http>
<!-- omitted -->
<sec:headers>
<sec:header ref="secureCacheControlHeadersWriter"/> <!-- (2) -->
</sec:headers>
<!-- omitted -->
</sec:http>
(1) Specify implementation class of RequestMatcher and HeadersWriter interface and define a
bean for DelegatingRequestMatcherHeaderWriter class.
(2) Add <sec:header> as child element of <sec:headers> element and specify a bean for
HeaderWriter defined in (1) in ref attribute.
9.7.1 Overview
It explains about Cross-site scripting (hereinafter abbreviated as XSS). Cross Site Scripting is injection of mali-
cious scripts across trusted web sites by deliberately using security defects in the web application. For example,
when data entered in Web Application (form input etc.) is output in HTML without appropriate escaping, the
characters of tag existing in input value are interpreted as HTML as is. If a script with malicious value is run,
attacks such as session hijack occur due to cookie tampering and fetching of cookie values.
In Stored XSS Attacks, the malicious code is permanently stored on target servers (such as database). Upon
requesting the stored information, the user retrieves the malicious script from the server and ends up running the
same.
In Reflected attacks, the malicious code sent as a part of the request to the server is reflected back along with error
messages, search results, or other different types of responses. When a user clicks the malicious link or submits a
specially crafted form, the injected code returns a result reflecting an occurrence of attack on user’s browser. The
browser ends up executing the malicious code because the value came from a trusted server.
Both Stored XSS Attacks and Reflected XSS Attacks can be prevented by escaping output value.
When the input from user is output as is, the system gets exposed to XSS vulnerability. Therefore, as a coun-
termeasure against XSS vulnerability, it is necessary to escape the characters which have specific meaning in the
HTML markup language.
Escaping types:
• Output Escaping
• JavaScript Escaping
Output Escaping
Escaping HTML special characters is a fundamental countermeasure against XSS vulnerability. Example of
HTML special characters that require escaping and example after escaping these characters are as follows:
& &
< <
> >
" "
’ '
To prevent XSS, f:h() should be used in all display items that are to be output as strings. An example of
application where input value is to be re-output on different screen is given below.
This example below is given only for reference; it should never be implemented.
By escaping special characters, input value is output as is without being recognized as <script> tag.
Output result
It is recommended that you use <fmt:formatDate> of JSTL to format and display java.util.Date subclasses.
See the example below.
If f:h() is used for setting the value of “value” attribute, it gets converted into String and
javax.el.ELException is thrown; hence ${form.date} is used as is. However, it is safe from XSS
attack since the value is in yyyyMMdd format.
It is recommended that you use <fmt:formatNumber> to format and display the string that can be parsed to
java.lang.Number subclasses or java.lang.Number. See the example below.
There is no problem even if the above is a String; hence when <fmt:formatNumber> tag is no longer used,
f:h()is being used explicitly so that no one forgets to use f:h().
JavaScript Escaping
Escaping JavaScript special characters is a fundamental countermeasure against XSS vulnerability. Escaping is
must if it is required to dynamically generate JavaScript based on the outside input.
Example of JavaScript special characters that require escaping and example after escaping these characters are as
follows:
’ \’
" \"
\ \\
/ \/
< \x3c
> \x3e
0x0D(Return) \r
0x0A(Linefeed) \n
This example below is given only for reference; it should never be implemented.
<html>
<script type="text/javascript">
var aaa = '<script>${warnCode}<\/script>';
document.write(aaa);
</script>
<html>
warnCode <script></script><script>alert(’XSS
Attack!’);</script><\/script>
As shown in the above example, in order to dynamically generate JavaScript elements such as generating the code
based on the user input, string literal gets terminated unintentionally leading to XSS vulnerability.
Output result
<script type="text/javascript">
var aaa = '<script><\/script><script>alert('XSS Attack!');<\/script><\/script>';
document.write(aaa);
</script>
.. tip::
Dynamically generated JavaScript code depending on user input carries a risk of any script being i
To prevent XSS, it is recommended that you use EL function f:js() for the value entered by user.
<script type="text/javascript">
var message = '<script>${f:js(message)}<\/script>'; // (1)
<!-- omitted -->
</script>
(1) By using f:js() of EL function, the value is set as variable after escaping the value entered by user.
Output result
<script type="text/javascript">
var aaa = '<script>\x3c\/script\x3e\x3cscript\x3ealert(\'XSS Attack!\');\x3c\/script\x3e<\/scr
document.write(aaa);
</script>
To escape the value of event handler of javascript, f:hjs() should be used instead of f:h() or f:js(). It is
equivalent to ${f:h(f:js())}.
This is because, when "’);alert("XSS Attack");// " is specified as event handler value such as
<input type="submit" onclick="callback(’xxxx’);">, different script gets inserted, after es-
caping the value in character reference format, escaping in HTML needs to be done.
Output result
(1) Value after escaping by EL function f:hjs() is set as an argument of javascript event handler.
Output result
9.8 Encryption
9.8.1 Overview
Encryption is required for confidential information like personal data, passwords etc in the following cases.
Spring Security also offers encryption related functions besides the major functions like “Authentication” and
“Authorization”.
However, since functions offered by Spring Security are limited, the encryption methods which are not supported
by Spring Security must be implemented independently.
• Encryption and decryption by common key encryption methods which use class offered by Spring Security
• Encryption and decryption by public key encryption method which use JCA (Java Cryptography Architec-
ture)
For details of Encryption function of Spring Security, refer Spring Security Reference -Spring Security Crypto
Module-.
Encryption method
It is a method which uses the same key during encryption and decryption.
Since the method shares a key used in decryption for the encryption, a different path to safely transfer the key to
encryption side is required.
It is a method wherein encryption is performed by using a public key offered by decryption side and decryption is
performed by using a secret key which is paired with the public key.
Secret key used while decrypting encrypted text is not published resulting in high security strength, however cost
of encryption and decryption is likely to be high.
It is a method which combines merits of common key encryption method wherein processing cost is less and
merits of public key encryption method wherein security strength is high due to easy management and
distribution of key.
This method is used in SSL/TLS etc.
For example, in HTTPS communication, common key generated at the client side is sent after encrypting it with
public key on the server side and common key is decrypted at the server by using secret key paired with public
key. The subsequent communication is done by a common key encryption method which uses a shared common
key.
• Confidential information which is likely to be large in size is encrypted by a common key encryption
method wherein the processing cost is less
• A common key with smaller size wherein safe distribution is required is encrypted by using a public key
encryption method with high security strength
Since common key used while decrypting confidential information is protected by secret key, security strength of
public key encryption method is retained while achieving a high-speed encryption and decryption process than
public key encryption method.
Process flow of hybrid encryption method from encryption to decryption is shown in the following figure..
5. Receiving side decrypts encrypted common key by using secret key on receiving side.
Encryption algorithm
DES / 3DES
DES (Data Encryption Standard) is an algorithm standardised in USA as an algorithm for common encryption
method. Currently, it is not recommended since key length is short at 56 bits.
3DES (triple DES) is an encryption algorithm which repeats DES while changing the key.
AES
AES (Advanced Encryption Standard) is an algorithm for common key encryption method. It is an encryption
standard established subsequent to DES and is used as a current default standard for encryption.
Also, ECB (Electronic Codebook), CBC (Cipher Block Chaining) and OFB (Output Feedback) are provided as
encryption mode wherein a message longer than the block length is encrypted. Of these, CBC is widely used.
GCM (Galois/Counter Mode) is an encryption mode generally accepted for feasible parallel processing and ex-
cellent processing efficiency as compared to CEC and can be used in AES.
RSA
RSA is an algorithm of public key encryption method. Ability of a computing machine impacts the algorithm
since it is based on difficulty of prime numbers factoring. It requires adequate key length as indicated in “Issues
of encryption algorithm in 2010”. At present, 2048 bits is used a standard length.
DSA / ECDSA
DSA (Digital Signature Algorithm) is a standard specification for digital signature. It is based on the difficulty of
discrete logarithmic problem.
ECDSA (Elliptic Curve Digital Signature Algorithm ) is a variant of DSA which uses Elliptic curve photography.
Elliptic curve photography offers an advantage of reducing key length which is necessary for maintaining
security level.
javax.crypto.Cipher class
Cipher class offers encryption and decryption functions, and specifies a combination of encryption algorithms
like AES or RSA, encryption modes like ECB or CBC and padding methods like PKCS1.
Encryption mode is a mechanism to encrypt messages longer than block length, as explained in AES.
Also, padding method is a method of storage when encryption target that does not meet the required block length
is to be encrypted.
Spring Security offers an encryption and decryption function which uses a common key encryption method.
Encryption algorithm is 256-bit AES using PKCS #5’s PBKDF2 (Password-Based Key Derivation Function #2).
Encryption mode is CBC and padding method is PKCS5Padding.
Spring Security offers following interfaces as a function for encryption and decryption using common key encryp-
tion method.
Also, following classes are offered as implementation classes for these interfaces and Cipher class is used
internally.
• org.springframework.security.crypto.encrypt.HexEncodingTextEncryptor
(For text)
Spring Security offers following interfaces as functions to generate random numbers (key).
Also, following classes are offered as implementation classes for these interfaces.
• org.springframework.security.crypto.keygen.HexEncodingStringKeyGenerator
(for text)
• org.springframework.security.crypto.keygen.SecureRandomBytesKeyGenerator
(for byte array. Generate a different key length by generateKey method and return)
spring-security-rsa offers API for public key encryption method and hybrid encryption method which use
RSA as an encryption algorithm. spring-security-rsa is currently not managed as official repository of Spring
<https://github.com/spring-projects>_. Later, how to use the repository will be explained in the guideline after
moving it under official repository of Spring.
• org.springframework.security.crypto.encrypt.RsaRawEncryptor
A class that offers encryption and decryption functions which use public key encryption method.
• org.springframework.security.crypto.encrypt.RsaSecretEncryptor
A class that offers encryption and decryption functions which use hybrid encryption method.
An unlimited strength JCE jurisdiction policy file must be applied for handling key length 256 bits of AES in some
Java products like Oracle.
Default encryption algorithm strength is restricted in some Java products due to relation with import regulations.
If a more powerful algorithm is to be used, an unlimited strength JCE jurisdiction policy file must be obtained and
installed in JDK/JRE. For details, refer Java Cryptography Architecture Oracle Providers Documentation.
(1) Call Encryptors#text method by specifying common key and salt, and generate instance
of TextEncryptor class.
Since initialization vector of generated instance is random, a varied result is returned at the time
of encryption. It should be noted that CEC is used as an encryption mode.
Common key and salt specified during encryption are also used at the time of decryption.
Return value of encrypt method (encryption results) return a different value for each execution, however,
if key and salt are identical, decryption process results will be similar as well (can be correctly decrypted).
This method is used in the processes such as searching the database etc.using encrypted results. However,
whether to use the method must be reviewed considering possible reduction in the security strength.
Sr.No. Description
(1) When identical value must be fetched as encryption results, generate an instance of
TextEncryptor class by using Encryptors#queryableText method.
AES using GCM can be used in Spring Security4.0.2 and subsequent versions. Processing efficiency is
superior to CEC as explained in AES.
(1) Call Encryptors#delux method by specifying common key and salt, and generate an
instance of TextEncryptor class.
Common key and salt specified during encryption are also used at the time of decryption.
AES using GCM can be used in Java SE8 and subsequent versions. For details, refer JDK 8 security
enhancement.
Decryption of string
(1) Call Encryptors#text method by specifying common key and salt, and generate an
instance of TextEncryptor class.
Specify values used at the time of encryption as common key and salt.
• Decrypt encrypted text of text (string) by using AES which uses GCM.
(1) Call Encryptors#delux method by specifying common key and salt, and generate an
instance of TextEncryptor class.
Specify values at the time of encryption as common key and salt.
(1) Call Encryptors#standard method by specifying common key and salt, and generate an
instance of BytesEncryptor class.
Common key and salt specified during encryption are also used at the time of decryption.
(1) Call Encryptors#stronger method by specifying common key and salt, and generate an
instance of BytesEncryptor class.
Common key and salt specified during encryption are also used at the time of decryption.
(1) Call Encryptors#standard method by specifying common key and salt, and generate an
instance of BytesEncryptor class.
Specify values used at the time of encryption as common key and salt.
(1) Call Encryptors#stronger method by specifying common key and salt, and generate an
instance of BytesEncryptor class.
Specify values used at the time of encryption as common key and salt.
Since functions related to public key encryption method are not offered by Spring Security, a method which uses
JCA and OpenSSL is explained using a sample code.
• Generate key pairs (a combination of public key / secret key) using JCA, perform encryption and decryption
process by using public key and secret key respectively.
(4) Use public key and perform encryption process. Processing details will be described later.
(5) Use secret key and perform decryption process. Processing details will be described later.
When encrypted data is to be handled as string like in external system linkage etc,
Base64 encoding can be listed as one of the measures. java.util.Base64 of Java
standard is used in case of subsequent versions of Java SE8. In the earlier versions,
org.springframework.security.crypto.codec.Base64 of Spring Security is used.
A method used for Base64 encoding and decoding is explained using java.util.Base64 of Java
standard.
– Base64 encoding
// omitted
byte[] cipherBytes = encryptByPublicKey("Hello World!", publicKey); // Encryption proces
String cipherString = Base64.getEncoder().encodeToString(cipherBytes); // Convert encryp
// omitted
– Base64 decoding
// omitted
byte[] cipherBytes = Base64.getDecoder().decode(cipherString); // Convert encrypted text
String plainText = decryptByPrivateKey(cipherBytes, privateKey); // Decryption process
// omitted
Encryption
Sr.No. Description
(1) Specify encryption algorithm, encryption mode and padding method, and generate an instance
of Cipher class.
Decryption
(1) Specify encryption algorithm, encryption mode and padding method, and generate an instance
of Cipher class.
OpenSSL
If Cipher is identical, a different method can be used for encryption and decryption for public key encryption
method.
Here, key pairs are created in advance by using OpenSSL and encryption is performed by JCA, by using this
public key. Hence, a method wherein decryption process is performed by OpenSSL, by using the secret key is
explained.
Note: OpenSSL
The software must be installed for creating key pairs by OpenSSL. It can be downloaded from the following site.
• For Linux
• For Windows
$ openssl pkcs8 -topk8 -nocrypt -in private.pem -out private.pk8 -outform DER # (2)
$ openssl rsa -pubout -in private.pem -out public.der -outform DER # (3)
(2) Convert secret key to PKCS#8 format for reading it from Java application.
• Read public key created by OpenSSL in the application and perform encryption process by using public
key that has been read.
Files.write(Paths.get("encryptedByJCA.txt"), cipherBytes);
System.out.println("Please execute the following command:");
System.out
.println("openssl rsautl -decrypt -inkey hoge.pem -in encryptedByJCA.txt");
} catch (IOException e) {
throw new SystemException("e.xx.xx.9001", "input/output error.", e);
} catch (NoSuchAlgorithmException e) {
throw new SystemException("e.xx.xx.9002", "No Such setting error.", e);
} catch (InvalidKeySpecException e) {
throw new SystemException("e.xx.xx.9003", "Invalid setting error.", e);
}
}
Sr.No. Description
Further, a method wherein encryption and decryption are performed by OpenSSL and JCA respectively using key
pairs created by OpenSSL is explained.
$ echo Hello | openssl rsautl -encrypt -keyform DER -pubin -inkey public.der -out encryptedBy
• Read secret key created by OpenSSL in the application and perform decryption process by using a secret
key that has been read.
(1) Read binary data from secret key file of PKCS #8 format and generate an instance of
PKCS8EncodedKeySpec class.
Similar to public key encryption method, since functions related to hybrid encryption methods are not offered by
Spring Security, it is explained using a sample code.
The sample code refers to RsaSecretEncryptor class of spring-security-rsa.
Encryption
result.write(secret); // (7)
result.write(aes.encrypt(plainBytes)); // (8)
(2) Specify generated common key and salt, and generate an instance of BytesEncryptor class.
(3) specify RSA as an encryption algorithm and generate an instance of Cipher class.
(4) Specify encryption mode constant and public key, and initialise an instance of Cipher class.
(5) Execute encryption process of common key. The encryption is performed by using public key
encryption process.
(6) Store length of encrypted common key in encrypted text of byte array. Length of stored
common key is used at the time of decryption.
(8) Encrypt plain text and store in encrypted text of byte array. The encryption is performed by
using common key encryption process.
Decryption
(3) Specify RSA as an encryption algorithm and generate an instance of Cipher class.
(4) Specify decryption mode constant and secret key, and initialise an instance of Cipher class.
(5) Execute decryption process of common key. Decryption is performed by using public key
encryption process.
(7) Specify decrypted common key and salt, and generate an instance of BytesEncryptor class.
(8) Execute decryption process. Decryption is performed by using common key encryption process.
When key length is not specified, a key of length - 8 bytes is generated by default.
(1) Specify 32 bytes as key length, call KeyGenerators#shared method and generate an
instance of key (pseudo-random number) generator BytesKeyGenerator class.
If key is generated by this generator, same value is obtained for each instance.
9.9.1 Introduction
• Example of implementation method to meet the typical security requirements using TERASOLUNA Server
Framework for Java (5.x)
• Implementation method and source code description using the sample application shown in Description of
application
Warning:
• The implementation methods described in this chapter is just an example, and implementation must be
carried out in the actual development as per the requirement.
• Since it does not guarantee an exhaustive implementation of security measures, additional measures
should be considered if necessary
Target Readers
This section explains about a specific implementation method for security measures by using a sample
application that meets the typical security requirements.
The list of security requirements that describe the implementation in this section is shown below. The functions
of the sample application used as a base and the specifications for authentication and authorization are also
shown below.
Henceforth, this sample application will be referred to as ‘the application’.
Security requirements
The list of security requirements fulfilled by the application is shown below. The implementation example is
described in Implementation method and code description for each classification.
Specify the type of characters for the password Specifies the type of
characters (uppercase
(5)
letters, lowercase let-
ters, numbers, sym-
bols) that must be in-
cluded in the password
Prohibit user name from being used as password Prohibit user name of
the account from be-
(6)
ing used in the pass-
word
Prohibit reuse of administrator password Prohibit reusing the
password which has
(7)
been recently used by
the administrator
Account lockout Account lockout If authentication of a
9.9. Implementation Example of Typical Security Requirements certain1889
account has
(8)
failed for more than
a specific number
of times within a
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Functions
The application consists of following functions in addition to the application created in Spring Security Tutorial.
Note: Since this application is a sample of security measures, it is essentially required. Update function for
registration information other than user registration function and password is not created.
In this application, the specifications for authentication/authorization are shown below respectively.
Authentication
Authorization
• Authentication is required to access the screens other than login screen, screen used for creating an account
and the screen used for password reissue
• Account unlock function can be used only by the account having administrator rights
•The following information created by the application is used for the password reissue authentication
•URL of the password reissue screen generated by the application is in the following format:
–{baseUrl}/reissue/resetpassword?form&token={token}
• A time-limit of 30 minutes is provided for the password reissue screen URL and authentication is possible
only within the validity period
Design information
Page transition
Screen transition diagram is shown below. Screen transition in case of an error is omitted.
password reissue
URL List
ER diagram
(2) Account image Image registered by the user for the account username : User name for the
account corresponding to the
account image
body : Binary image file
extension : Extension of image
file
(5) Authentication failed Information saved when authentication failed username : User name
event to be used by account lockout function authenticationTimestamp : Date
and time when authentication
failed
(6) Password change Information saved at the time of password username : User name
history change to be used to determine password useFrom : Date and time when
1896 9 Security countermeasures
expiration date changed password is activated
password : Changed password
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Tip: In order to determine initial password and password expiration, a design can also be adopted wherein the
information such as last modified date and time of password is provided by adding a field to the account entity.
When implementation is done using this method, it is likely to lead to a situation where a column is added for
determining various conditions in account table and entries are frequently updated.
In this application, table is maintained in a simple form. In order to fulfil the requirements by simply using Insert
and Delete without unnecessary updates of the entries, a design using event entity such as authentication successful
event entity has been adopted.
Method of implementation in this application and the code are described for each classification of security
requirements.
Only the minimum code required to fulfil the requirements for each classification is described here. Refer to
GitHub for the complete code.
SQL for initial data registration to run this application is placed here.
Note: In this application, Lombok is used to eliminate boilerplate code. For Lombok, refer Reducing Boilerplate
Code (Lombok).
Working image
Implementation method
In this application, the history when password is changed is stored as “Password change history” entity in the
database. Using this password change history entity, the initial password and password expiration are determined.
Note that, redirecting to the password change screen and displaying message on the screen are controlled based
on the determination result.
In particular, requirements are fulfilled by implementing and using the following process.
When the password is changed, register password change history entity containing following information
to the database.
After authentication, search the password change history entity of the authenticated account from the
database. If even a single record is not found, consider that initial password is being used.
Otherwise, get the latest password change history entity, calculate the difference between current date and
time, and date and time when the password is activated and determine whether the password has expired.
To force password change, the user is redirected to the password change screen in case a request is raised
for a screen other than password change screen, when the conditions below are met.
Tip: There are other methods to redirect to the password change screen after authentication, however, de-
pending on the method, it is likely that user gets access to a screen different from that of a password change
screen by clicking the URL directly after redirecting. In the method that uses HandlerInterceptor
, it cannot be avoided by a method wherein URL is directly clicked since the process is executed before
executing handler method.
Tip: Servlet Filter can also be used instead of HandlerInterceptor . For both the descrip-
tions, refer to Implementing common logic to be executed before and after calling controller. Here,
HandlerInterceptor is used to perform processing for only the requests allowed by the application.
Call the password expiration determination process described previously in the Controller. Pass the deter-
mination result to View, and switch show/hide message in View.
Code description
The code implemented according to the implementation method mentioned above is described sequentially.
A series of implementations to register password change history entity in the database at the time of chang-
ing the password is shown below.
– Implementation of Entity
package org.terasoluna.securelogin.domain.model;
// omitted
@Data
public class PasswordHistory {
(1) User name of the account for which the password is changed
– Implementation of Repository
The Repository to register and search password change history entity to the database is shown below.
package org.terasoluna.securelogin.domain.repository.passwordhistory;
// omitted
List<PasswordHistory> findLatest(
@Param("username") String username, @Param("limit") int limit); // (3)
(1) A method to register PasswordHistory object that has been assigned as an argument,
as a record in the database
(2) A method to get PasswordHistory object newer than the date specifying the date and
time when the password is activated, in descending order (new order) by considering user
name that has been assigned as an argument, as the key
<mapper
namespace="org.terasoluna.securelogin.domain.repository.passwordhistory.PasswordHisto
SELECT
username,
password,
use_from
FROM
password_history
WHERE
username = #{username}
ORDER BY use_from DESC
LIMIT #{limit}
]]>
</select>
– Service implementation
Password change history entity operations are also used in Check password strength. Therefore, call
the Repository method from SharedService as shown below.
package org.terasoluna.securelogin.domain.service.passwordhistory;
// omitted
@Service
@Transactional
public class PasswordHistorySharedServiceImpl implements
PasswordHistorySharedService {
@Inject
PasswordHistoryRepository passwordHistoryRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insert(PasswordHistory history) {
return passwordHistoryRepository.create(history);
}
@Transactional(readOnly = true)
@Override
@Transactional(readOnly = true)
public List<PasswordHistory> findLatest(String username, int limit) {
return passwordHistoryRepository.findLatest(username, limit);
}
Implementation of the process to save password change history entity in the database at the time of
changing the password is shown below.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordHistorySharedService passwordHistorySharedService;
@Inject
AccountRepository accountRepository;
@Inject
PasswordEncoder passwordEncoder;
// omitted
return result;
// omitted
}
(3) Create password change history entity and set user name, changed password, and date and
time when changed password is activated.
(4) Call the process to register the created password change history entity in the database.
Using the password change history entity registered in the database, implementation of the process to
determine whether initial password is used and whether the password has expired is shown below.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordHistorySharedService passwordHistorySharedService;
@Value("${security.passwordLifeTimeSeconds}") // (1)
int passwordLifeTimeSeconds;
// omitted
@Transactional(readOnly = true)
@Override
@Cacheable("isInitialPassword")
public boolean isInitialPassword(String username) { // (2)
List<PasswordHistory> passwordHistories = passwordHistorySharedService
.findLatest(username, 1); // (3)
return passwordHistories.isEmpty(); // (4)
}
@Transactional(readOnly = true)
@Override
@Cacheable("isCurrentPasswordExpired")
public boolean isCurrentPasswordExpired(String username) { // (5)
List<PasswordHistory> passwordHistories = passwordHistorySharedService
.findLatest(username, 1); // (6)
if (passwordHistories.isEmpty()) { // (7)
return true;
}
if (passwordHistories
.get(0)
.getUseFrom()
.isBefore(
dateFactory.newTimestamp().toLocalDateTime()
.minusSeconds(passwordLifeTimeSeconds))) { // (8)
return true;
}
return false;
}
(1) Fetch the length of the time period (in seconds) for which password is valid, from the property
file and set.
(2) A method which determines whether initial password is used, and returns true if it is used, or
else returns false.
(3) Call the process to fetch single record of the latest password change history entity from the
database.
(4) If password change history entity cannot be fetched from the database, determine that initial
password is being used and return true. Otherwise, return false.
(5) A method which determines whether the password currently being used has expired and returns
true if it has expired, or else returns false.
(6) Call the process to fetch single record of the latest password change history entity from the
database.
(7) If password change history entity cannot be fetched from the database, determine that the
password has expired and return true.
(8) If difference between the current date and time, and the date and time when the password
fetched from the password change history entity is activated, is greater than the password
validity period set in (1), determine that the password has expired and return true.
(9) If any of the conditions of (7), (8) is not met, determine that the password is within the validity
period and return false.
Further, while using cache, it should be noted that it is necessary to clear the cache as and when needed.
In this application, at the time of changing the password or during logout, clear the cache to determine
password expiration and determine initial password again.
Further, set cache TTL (Time to Live) as needed. Note that TTL is not set depending on the implementation
of the cache to be used.
In order to enforce password change, the implementation of the process to be redirected to the password
change screen is shown below.
package org.terasoluna.securelogin.app.common.interceptor;
// omitted
@Inject
AccountSharedService accountSharedService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws IOException { // (2)
Authentication authentication = (Authentication) request
.getUserPrincipal();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof UserDetails) { // (3)
LoggedInUser userDetails = (LoggedInUser) principal; // (4)
if ((userDetails.getAccount().getRoles().contains(Role.ADMIN) && accountShare
.isCurrentPasswordExpired(userDetails.getUsername())) // (5)
|| accountSharedService.isInitialPassword(userDetails
.getUsername())) { // (6)
response.sendRedirect(request.getContextPath()
+ "/password?form"); // (7)
return false; // (8)
}
}
}
return true;
}
}
(1) Inherit
org.springframework.web.servlet.handler.HandlerInterceptorAdapter
to include the process before the execution of handler method of Controller.
(4) Fetch UserDetails object . In this application, a class called LoggedInUser is created
and used as the implementation of UserDetails .
(5) Determine whether the user is an administrator by fetching the role from UserDetails
object. Then, call the process to determine whether the password has expired. Perform logical
AND (And) of these two results.
(6) Call the process to determine whether initial password is being used.
(7) If either (5) or (6) is true, redirect to the password change screen using sendRedirect
method of javax.servlet.http.HttpServletResponse .
(8) Return false to prevent handler method of Controller being executed continuously.
The settings to enable the redirect process described above are as described below.
spring-mvc.xml
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" /> <!-- (1) -->
<mvc:exclude-mapping path="/password/**" /> <!-- (2) -->
<mvc:exclude-mapping path="/reissue/**" /> <!-- (3) -->
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.securelogin.app.common.interceptor.PasswordExpirationCheckI
</mvc:interceptor>
</mvc:interceptors>
(2) Exclude the paths under “/password” to prevent redirecting from password change screen to
password change screen.
(3) Exclude the paths under “/reissue” since it is not necessary to check password expiration at the
time of reissuing password.
Implementation of Controller to display message prompting password change on top screen is shown below.
package org.terasoluna.securelogin.app.welcome;
// omitted
@Controller
public class HomeController {
@Inject
AccountSharedService accountSharedService;
model.addAttribute("account", account);
if(accountSharedService.isCurrentPasswordExpired(account.getUsername())){ // (3)
ResultMessages messages = ResultMessages.warning().add(
"w.sl.pe.0001");
model.addAttribute(messages);
}
// omitted
return "welcome/home";
(3) Call the password expiration determination process by using the user name obtained from
account information as an argument. If the result is true, fetch the message from the property
file, set it in Model and pass it to View.
Top screen(home.jsp)
<body>
<div id="wrapper">
<span id="expiredMessage">
<t:messagesPanel /> <!-- (1) -->
</span>
</div>
</body>
(1) Using messagesPanel tag, display the password expiration message passed from the Controller.
Working image
Implementation method
Input Validation function can be used to verify the strength of the password specified by the user at the time of
password change. In this application, the strength of the password is verified by using Bean Validation.
Requirements for password strength are wide-ranging and differ depending on the application.
Use Passay as the library for password validation and create the required Bean Validation annotation.
Many functions that are commonly used in password validation have been provided in Passay. The functions that
have not been provided can also be easily implemented by extending the standard functions.
Refer to Appendix for the overview of Passay.
In particular, describe the following settings and process and fulfil the requirements using them.
– Validation rule wherein the character type that must be included in the password is set
– Validation rule to check that the password does not contain user name
– Validation rule to check that same password has not been used recently
Create Passay validator wherein validation rules created above, are set.
Create an annotation for password validation using Passay validator. All the validation rules can also be
verified by one annotation, however, verifying various rules leads to complex process and reduced visibility.
To avoid this, it should be implemented by dividing into two as shown below.
Check the three validation rules, “Password is longer than the minimum string length”, “Password
includes characters of the specified character type”, and “Password does not contain user name”
Check that the recently used password is not reused by the administrator in recent period of time.
Any annotation is a correlation input check rule using user name and new password. When a violation
occurs in either of the inputs of both the rules, respective error message is displayed.
• Password validation
Code description
The code implemented according to the implementation method mentioned above is described sequentially. Pass-
word validation using Passay is described in Password validation.
Most of the verification rules used in this application can be defined by using the class provided in Passay
by default.
However, in the class provided by Passay, validation rule to compare with the hashed previous password
cannot be defined in
org.springframework.security.crypto.password.PasswordEncoder .
Therefore, it is necessary to create a class with individual validation rules by extending the class provided
by Passay as shown below.
package org.terasoluna.securelogin.app.common.validation.rule;
// omitted
@Override
protected boolean matches(final String clearText,
final PasswordData.Reference reference) { // (3)
return passwordEncoder.matches(clearText, reference.getPassword()); // (4)
}
}
applicationContext.xml
(1) In org.passay.LengthRule property to check the password length, set the minimum
length of the password fetched from the property file.
(2) A validation rule to check that one or more single-byte upper-case letters are included. Set
org.passay.EnglishCharacterData.UpperCase and numerical value 1 in
org.passay.CharacterRule constructor to check the character type included in the
password.
(3) A validation rule to check that one or more single-byte lower-case letters are included. Set
org.passay.EnglishCharacterData.LowerCase and numerical value 1 in
org.passay.CharacterRule constructor to check the character type included in the
password.
(4) A validation rule to check that one or more single-byte digits are included. Set
org.passay.EnglishCharacterData.Digit and numerical value 1 in
org.passay.CharacterRule constructor to check for the character type included in the
password.
(5) A validation rule to check that one or more single-byte symbols are included. Set
org.passay.EnglishCharacterData.Special and numerical value 1 in
org.passay.CharacterRule constructor to check for the character type included in the
password.
(6) A validation rule to check that 3 out of the 4 validation rules from (2)-(5) are met. Set Bean list
defined in (2)-(5) and numerical value 3 in
org.passay.CharacterCharacteristicsRule property.
(7) A validation rule to check that password does not contain user name
(8) A validation rule to check that the password is not included in the passwords used in the past
Using validation rules of Passay described above, Bean definition for validator to perform actual validation
is shown below.
applicationContext.xml
(1) A validator to validate the characteristics of the password. Set a Bean for LengthRule ,
CharacterCharacteristicsRule , UsernameRule as a property.
(2) A validator to check the history of the passwords that were used in the past. Set a Bean for
EncodedPasswordHistoryRule as a property.
To fulfil the requirements, create two annotations that use the validator described above.
The implementation of the annotation to check three validation rules - ‘password should be longer than
the minimum string length, it should contain characters of specified character type and it should not
contain user name’ is shown below.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Inject
@Named("characteristicPasswordValidator") // (1)
PasswordValidator characteristicPasswordValidator;
@Override
public void initialize(StrongPassword constraintAnnotation) {
usernamePropertyName = constraintAnnotation.usernamePropertyName();
newPasswordPropertyName = constraintAnnotation.newPasswordPropertyName();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
String username = (String) beanWrapper.getPropertyValue(usernamePropertyName);
String newPassword = (String) beanWrapper
.getPropertyValue(newPasswordPropertyName);
if (result.isValid()) { // (3)
return true;
} else {
context.disableDefaultConstraintViolation();
for (String message : characteristicPasswordValidator
.getMessages(result)) { // (4)
context.buildConstraintViolationWithTemplate(message)
.addPropertyNode(newPasswordPropertyName)
.addConstraintViolation();
}
return false;
}
}
}
(3) Confirm the check result, if it is OK, return true, else return false.
(4) Fetch and set all the password validation error messages.
Implementation of the annotation to check that the administrator does not reuse the password used
earlier within a short period of time, is shown below.
Password change history entity is used to get the password used in the past. Refer to Force/Prompt
password change for the password change history entity.
Note: In the setting of “Prevent reuse of password used before specified period”, it is possible to reuse
a password by repeating a password within a short period of time. In order to prevent this, check is
performed in this application by setting “Prevent reuse of password used from a certain period onwards
package org.terasoluna.securelogin.app.common.validation;
@Documented
@Constraint(validatedBy = { NotReusedPasswordValidator.class }) // (1)
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface NotReusedPassword {
String message() default "{org.terasoluna.securelogin.app.common.validation.NotReused
(2) A property to specify the property name of the user name. It is required to search the
password used in the past, from the database.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Inject
ClassicDateFactory dateFactory;
@Inject
AccountSharedService accountSharedService;
@Inject
PasswordHistorySharedService passwordHistorySharedService;
@Inject
PasswordEncoder passwordEncoder;
@Inject
@Named("encodedPasswordHistoryValidator") // (1)
PasswordValidator encodedPasswordHistoryValidator;
@Value("${security.passwordHistoricalCheckingCount}") // (2)
int passwordHistoricalCheckingCount;
@Value("${security.passwordHistoricalCheckingPeriod}") // (3)
int passwordHistoricalCheckingPeriod;
@Override
public void initialize(NotReusedPassword constraintAnnotation) {
usernamePropertyName = constraintAnnotation.usernamePropertyName();
newPasswordPropertyName = constraintAnnotation.newPasswordPropertyName();
message = constraintAnnotation.message();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
BeanWrapper beanWrapper = new BeanWrapperImpl(value);
String username = (String) beanWrapper.getPropertyValue(usernamePropertyName);
String newPassword = (String) beanWrapper
.getPropertyValue(newPasswordPropertyName);
return result;
}
if (result.isValid()) { // (10)
return true;
} else {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(
encodedPasswordHistoryValidator.getMessages(result).get(0)) // (11)
.addPropertyNode(newPasswordPropertyName).addConstraintViolation();
return false;
}
}
}
(2) Fetch the threshold to prohibit reuse of password up to a previous date, from the property
file and inject it.
(3) Fetch the threshold (in seconds) to prohibit the reuse of the password used from a date
onwards, from the property file and inject it.
(4) Call the process to check whether new password is different from the currently used
password. Perform this check regardless of the general user / administrator.
(5) In case of administrator, call the process to check that new password is not included in the
previously used passwords.
(6) Fetch the number of password change history entities specified in (2) and the password
change history entities of the period specified in (3) and use the larger number of the two
for the subsequent checks.
(7) In order to make a comparison with the previous password using Passay validator, fetch the
password from the password change history entity and create a list of
org.passay.PasswordData.HistoricalReference .
(10) Confirm the check result, if it is OK, return true, else return false.
1924 9 Security countermeasures
• Password validation
Perform password validation in the application layer which use Bean Validation annotation. Since input
check other than Null check is covered by the annotation assigned to Form class, only @NotNull is
assigned as a single item check.
package org.terasoluna.securelogin.app.passwordchange;
// omitted
import lombok.Data;
@Data
@Compare(source = "newPasssword", destination = "confirmNewPassword", operator = Compare.Oper
@StrongPassword(usernamePropertyName = "username", newPasswordPropertyName = "newPassword") /
@NotReusedPassword(usernamePropertyName = "username", newPasswordPropertyName = "newPassword"
@ConfirmOldPassword(usernamePropertyName = "username", oldPasswordPropertyName = "oldPassword
public class PasswordChangeForm implements Serializable{
@NotNull
private String username;
@NotNull
private String oldPassword;
@NotNull
private String newPassword;
@NotNull
private String confirmNewPassword;
(1) An annotation to check whether second input of new password is identical with the first input.
Refer to terasoluna-gfw-common check rules for the details.
(4) An annotation to check that the entered current password is correct. Definition will be omitted.
package org.terasoluna.securelogin.app.passwordchange;
// omitted
@Controller
@RequestMapping("password")
public class PasswordChangeController {
@Inject
PasswordChangeService passwordService;
// omitted
@RequestMapping(method = RequestMethod.POST)
public String change(@AuthenticationPrincipal LoggedInUser userDetails,
@Validated PasswordChangeForm form, BindingResult bindingResult, // (1)
Model model) {
passwordService.updatePassword(form.getUsername(),
form.getNewPassword());
return "redirect:/password?complete";
}
// omitted
(1) A handler method called at the time of changing the password. Perform validation by assigning
@Validated annotation to Form in the parameter.
(2) Confirm that the user name for password change and the user name of the logged-in account are
identical. If the two users are different, the user is again taken to the password change screen.
Note: In this application, user name is fetched from the Form to perform password validation using the
user name in Bean Validation. It is assumed that in View, the user name set in Model is retained as hidden,
however, since there is a risk of tampering, user name obtained from the Form before password change is
confirmed.
Account lock
• Account lock
Working image
• Account lock
In the login form, if you try to authenticate a user name with an incorrect password for a certain number of times,
successively in a short duration, then that user’s account will be locked. Locked account is not authenticated even
if a set of correct user name and password is entered.
Locked status is cancelled after a certain period of time or by unlocking it.
• Unlock
Unlock function can be used only when the user having administrator rights has logged in. If unlocking is carried
out by entering the user name for which the locked status is to be resolved, then the account of that user returns to
the status wherein authentication can be done again.
Implementation method
org.springframework.security.authentication.LockedException .
By using this function, if only the process set in UserDetails is implemented by determining whether the
account is locked, lockout function can be implemented.
In this application, the history of authentication failure is stored in the database as an “authentication failure
event” entity, and the lockout status of the account is determined using this authentication failure event entity.
In particular, each requirement related to account lockout is fulfilled by implementing and using the following
three processes.
In case of authentication failure due to invalid authentication information input, the events generated by
Spring Security are handled and the user name used for authentication and the date and time when authen-
tication was attempted are registered in the database as authentication failure event entity.
For some accounts, if a certain number of new authentication failure event entities at the current time
are more than a certain fixed number, the corresponding account is determined to be locked. Call this
determination process during authentication and set the determination results in the implementation class
of UserDetails .
Warning: Since authentication failure event entity is intended only to determine lockout, it is deleted when it
is no longer required. A separate log should be always saved when authentication log is required.
The working example of lockout function which uses authentication failure event entity is described with the
help of the following figure. Lockout by authentication failure for 3 times and lockout duration of 10 minutes is
considered as an example.
(1) Authentication with incorrect password has been attempted three times in last 10 minutes, and
authentication failure event entities for all the three occasions are stored in the database.
Therefore, it is determined that the account is locked.
(2) Authentication failure event entities for 3 occasions are stored in the database.
However, since authentication failure event entities are only for the two occasions in last 10 minutes,
the account is determined to be “not locked”.
(1) Authentication with incorrect password has been attempted three times in last 10 minutes.
Thereafter, since the authentication failure event entity is deleted, authentication failure event entity is
not stored in the database and the account is determined as “not locked”.
Code description
• Common part
In this application, registration, search and deletion of authentication failure event entity for the database is
commonly required to implement the functions related to account lockout. Therefore, the implementation
of domain layer / infrastructure layer related to the authentication failure event entity is shown first.
– Implementation of Entity
The implementation of authentication failure event entity with user name and date and time when
authentication was attempted is shown below.
package org.terasoluna.securelogin.domain.model;
// omitted
@Data
public class FailedAuthentication implements Serializable {
private static final long serialVersionUID = 1L;
– Implementation of Repository
Repository to search, register and delete authentication failure event entity is shown below.
package org.terasoluna.securelogin.domain.repository.authenticationevent;
// omitted
List<FailedAuthentication> findLatest(
@Param("username") String username, @Param("count") long count); // (2)
(3) A method to delete the authentication failure event entity records collectively by
considering user name assigned as an argument, as the key
<mapper
namespace="org.terasoluna.securelogin.domain.repository.authenticationevent.FailedAuthe
<resultMap id="failedAuthenticationResultMap"
type="FailedAuthentication">
<id property="username" column="username" />
<id property="authenticationTimestamp" column="authentication_timestamp" />
</resultMap>
authentication_timestamp
) VALUES (
#{username},
#{authenticationTimestamp}
)
]]>
</insert>
<delete id="deleteByUsername">
<![CDATA[
DELETE FROM
failed_authentication
WHERE
username = #{username}
]]>
</delete>
</mapper>
– Implementation of Service
The service to call the method of the created Repository is defined as below.
package org.terasoluna.securelogin.domain.service.authenticationevent;
// omitted
@Service
@Transactional
public class AuthenticationEventSharedServiceImpl implements
AuthenticationEventSharedService {
// omitted
@Inject
ClassicDateFactory dateFactory;
@Inject
FailedAuthenticationRepository failedAuthenticationRepository;
@Inject
AccountSharedService accountSharedService;
@Transactional(readOnly = true)
@Override
public List<FailedAuthentication> findLatestFailureEvents(
String username, int count) {
return failedAuthenticationRepository.findLatestEvents(username, count);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void authenticationFailure(String username) { // (1)
if (accountSharedService.exists(username)){
FailedAuthentication failureEvents = new FailedAuthentication();
failureEvents.setUsername(username);
failureEvents.setAuthenticationTimestamp(dateFactory.newTimestamp()
.toLocalDateTime());
failedAuthenticationRepository.create(failureEvents);
}
}
@Override
public int deleteFailureEventByUsername(String username) {
return failedAuthenticationRepository.deleteByUsername(username);
}
// omitted
(1) A method to create authentication failure event entity and register in the database.
If account of the user name received as an argument does not exist, skip the process of
registration to the database since it violates the foreign key constraints of the database.
Since it is likely that authentication failure event entity is not registered by the exception
after executing this method, REQUIRES_NEW is specified in the propagation method of
transaction.
The code implemented according to the implementation method is described below sequentially.
Use @EventListener annotation to execute the process by handling the event generated at the time
of authentication failure. For handling of event by using @EventListener annotation, refer Handling
authentication event .
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Component
public class AccountAuthenticationFailureBadCredentialsEventListener{
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@EventListener // (1)
public void onApplicationEvent(
AuthenticationFailureBadCredentialsEvent event) {
authenticationEventSharedService.authenticationFailure(username); // (3)
}
(3) Call the process to create authentication failure event entity and register in the database.
The process to determine account lockout status using authentication failure event entity is described.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
// omitted
@Inject
ClassicDateFactory dateFactory;
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@Value("${security.lockingDurationSeconds}") // (1)
int lockingDurationSeconds;
@Value("${security.lockingThreshold}") // (2)
int lockingThreshold;
@Transactional(readOnly = true)
@Override
public boolean isLocked(String username) {
List<FailedAuthentication> failureEvents = authenticationEventSharedService
.findLatestFailureEvents(username, lockingThreshold); // (3)
if (failureEvents
.get(lockingThreshold - 1) // (5)
.getAuthenticationTimestamp()
.isBefore(
dateFactory.newTimestamp().toLocalDateTime()
.minusSeconds(lockingDurationSeconds))) {
return false;
}
return true;
}
// omitted
}
(1) Specify the lockout duration in seconds. The value defined in Property file is injected.
(2) Specify the locking threshold. The account is locked when authentication fails only for the
number of times specified here. The value defined in Property file is injected.
(3) Fetch the authentication failure event entity in new sequence only for the number same as the
locking threshold.
(4) If the number of fetched authentication failure event entities is less than the locking threshold
value, determine that the account is not locked.
(5) If the difference between oldest authentication failure time from the fetched authentication
failure event entities and current time is greater than the locking duration, determine that the
account is not locked.
package org.terasoluna.securelogin.domain.service.userdetails;
// omitted
// omitted
LocalDateTime lastLoginDate,
List<SimpleGrantedAuthority> authorities) {
super(account.getUsername(), account.getPassword(), true, true, true,
!isLocked, authorities); // (1)
this.account = account;
// omitted
}
// omitted
}
(1) In the constructor of User which is the parent class, pass ** Whether the account is locked**
in truth-value. Note that it is necessary to pass true if the account is not locked.
package org.terasoluna.securelogin.domain.service.userdetails;
// omitted
@Service
public class LoggedInUserDetailsService implements UserDetailsService {
@Inject
AccountSharedService accountSharedService;
@Transactional(readOnly = true)
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
try {
Account account = accountSharedService.findOne(username);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : account.getRoles()) {
authorities.add(new SimpleGrantedAuthority("ROLE_"
+ role.getRoleValue()));
}
return new LoggedInUser(account,
accountSharedService.isLocked(username), // (1)
accountSharedService.getLastLoginDate(username),
authorities);
} catch (ResourceNotFoundException e) {
throw new UsernameNotFoundException("user not found", e);
}
(1) In constructor of LoggedInUser , pass the determination result of lockout status using
isLocked method.
spring-security.xml
<sec:authentication-manager>
<sec:authentication-provider
user-service-ref="loggedInUserDetailsService"> <!-- (1) -->
<sec:password-encoder ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
Since only consecutive authentication failures are used to determine lockout, delete the authentication
failure event entity of the account when authentication is successful. Create the method to be executed
when authentication is successful in the Service created as a common part.
package org.terasoluna.securelogin.domain.service.authenticationevent;
// omitted
@Service
@Transactional
public class AuthenticationEventSharedServiceImpl implements
AuthenticationEventSharedService {
// omitted
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void authenticationSuccess(String username) {
// omitted
deleteFailureEventByUsername(username); // (1)
}
// omitted
(1) Delete the authentication failure event entity for the account of the user name passed as an
argument.
Use @EventListener annotation to execute the process by handling the event generated when
authentication is successful.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Component
public class AccountAuthenticationSuccessEventListener{
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@EventListener // (1)
public void onApplicationEvent(
AuthenticationSuccessEvent event) {
authenticationEventSharedService.authenticationSuccess(details.getUsername()); //
(2) Fetch user name from AuthenticationSuccessEvent and call the process to delete
authentication failure event entity.
– Unlocking
Since authentication failure event entity is used to determine lockout status, an account can be unlocked
by deleting the authentication failure event entity. Perform the authorization settings to restrict the
usage of unlock function to the user having administrator rights and implement the domain layer /
application layer.
* Authorization settings
Set the rights for the user who can unlock an account as below.
spring-security.xml
</sec:http>
(1) Restrict the access rights for URL under /unlock to the administrator.
* Implementation of Service
package org.terasoluna.securelogin.domain.service.unlock;
// omitted
@Transactional
@Service
public class UnlockServiceImpl implements UnlockService {
@Inject
AccountSharedService accountSharedService;
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@Override
public void unlock(String username) {
authenticationEventSharedService.deleteFailureEventByUsername(username); // (1
}
* Implementation of Form
package org.terasoluna.securelogin.app.unlock;
@Data
public class UnlockForm implements Serializable {
@NotEmpty
private String username;
}
* Implementation of View
Top screen(home.jsp)
<body>
<div id="wrapper">
<div>
<a id="unlock" href="${f:h(pageContext.request.contextPath)}/unlock?form">
Unlock Account
</a>
</div>
</sec:authorize>
</div>
</body>
(1) Display only for the user who has access rights, under /unlock.
Unlock form(unlokcForm.jsp)
<body>
<div id="wrapper">
<h1>Unlock Account</h1>
<t:messagesPanel />
<form:form action="${f:h(pageContext.request.contextPath)}/unlock"
method="POST" modelAttribute="unlockForm">
<table>
<tr>
<th><form:label path="username" cssErrorClass="error-label">Userna
</th>
<td><form:input path="username" cssErrorClass="error-input" /></td
<td><form:errors path="username" cssClass="error-messages" /></td>
</tr>
</table>
<body>
<div id="wrapper">
<h1>${f:h(username)}'s account was successfully unlocked.</h1>
<a href="${f:h(pageContext.request.contextPath)}/">go to Top</a>
</div>
</body>
* Implementation of Controller
package org.terasoluna.securelogin.app.unlock;
// omitted
@Controller
@RequestMapping("/unlock") // (1)
public class UnlockController {
@Inject
UnlockService unlockService;
@RequestMapping(params = "form")
public String showForm(UnlockForm form) {
return "unlock/unlockForm";
}
@RequestMapping(method = RequestMethod.POST)
public String unlock(@Validated UnlockForm form,
BindingResult bindingResult, Model model,
RedirectAttributes attributes) {
if (bindingResult.hasErrors()) {
return showForm(form);
}
try {
unlockService.unlock(form.getUsername()); // (2)
attributes.addFlashAttribute("username", form.getUsername());
return "redirect:/unlock?complete";
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return showForm(form);
}
}
(1) Map to the URL under /unlock. It can be accessed by administrator only depending on
the authorization settings.
(2) Call the process to unlock an account by considering the user name obtained from the
Form, as an argument.
Working image
Implementation method
In this application, the history when authentication is successful is stored in the database as an “authentication
successful event” entity, and date and time of previous login for the account is displayed on the top screen using
this authentication successful event entity.
In particular, fulfil the requirements by implementing the following two processes.
Handle the event generated by Spring Security when authentication is successful and register the username
used for authentication and the date and time when authentication was successful in the database, as an
authentication successful event entity.
At the time of authentication, fetch the latest authentication successful event entity in the account
from the database, fetch the authentication successful date and time from the event entity and set in
org.springframework.security.core.userdetails.UserDetails . Format, pass and
display the authentication successful date and time retained by UserDetails in jsp.
Code description
• Common part
In this application, the authentication successful event entity must be registered and searched for the
database in order to display previous login date and time. Therefore, the implementation of domain layer /
infrastructure layer related to the authentication successful event entity is described first.
– Implementation of Entity
The implementation of authentication successful event entity with user name and date and time when
authentication was successful is as below.
package org.terasoluna.securelogin.domain.model;
// omitted
@Data
public class SuccessfulAuthentication implements Serializable {
– Implementation of Repository
Repository to search and register authentication successful event entity is shown below.
package org.terasoluna.securelogin.domain.repository.authenticationevent;
// omitted
List<SuccessfulAuthentication> findLatestEvents(
@Param("username") String username, @Param("count") long count); // (2)
}
<mapper
namespace="org.terasoluna.securelogin.domain.repository.authenticationevent.Successfu
<resultMap id="successfulAuthenticationResultMap"
type="SuccessfulAuthentication">
<id property="username" column="username" />
<id property="authenticationTimestamp" column="authentication_timestamp" />
</resultMap>
</insert>
– Implementation of Service
The service to call the methods of the created Repository is shown below.
package org.terasoluna.securelogin.domain.service.authenticationevent;
// omitted
@Service
@Transactional
public class AuthenticationEventSharedServiceImpl implements
AuthenticationEventSharedService {
// omitted
@Inject
ClassicDateFactory dateFactory;
@Inject
SuccessfulAuthenticationRepository successAuthenticationRepository;
@Transactional(readOnly = true)
@Override
public List<SuccessfulAuthentication> findLatestSuccessEvents(
String username, int count) {
return successAuthenticationRepository.findLatestEvents(username, count);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void authenticationSuccess(String username) {
SuccessfulAuthentication successEvent = new SuccessfulAuthentication();
successEvent.setUsername(username);
successEvent.setAuthenticationTimestamp(dateFactory.newTimestamp()
.toLocalDateTime());
successAuthenticationRepository.create(successEvent);
deleteFailureEventByUsername(username);
}
The code implemented according to the implementation method is described below sequentially.
Use @EventListener annotation to execute the process by handling the event generated when authen-
tication is successful.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Component
public class AccountAuthenticationSuccessEventListener{
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@EventListener // (1)
public void onApplicationEvent(AuthenticationSuccessEvent event) {
LoggedInUser details = (LoggedInUser) event.getAuthentication()
.getPrincipal(); // (2)
authenticationEventSharedService.authenticationSuccess(details
.getUsername()); // (3)
}
(3) Call the process to create authentication successful event entity and register in the database.
The Service to fetch date and time of previous login from authentication successful event entity is shown
below.
package org.terasoluna.securelogin.domain.service.account;
// omitted
@Service
@Transactional
public class AccountSharedServiceImpl implements AccountSharedService {
// omitted
@Inject
AuthenticationEventSharedService authenticationEventSharedService;
@Transactional(readOnly = true)
@Override
public LocalDateTime getLastLoginDate(String username) {
List<SuccessfulAuthentication> events = authenticationEventSharedService
.findLatestSuccessEvents(username, 1); // (1)
if (events.isEmpty()) {
return null; // (2)
} else {
return events.get(0).getAuthenticationTimestamp(); // (3)
}
}
// omitted
(1) Fetch one record of the latest authentication successful event entity by considering the user
name assigned as an argument, as the key.
(2) Return null if even a single record of authentication successful event entity could not be fetched
at the time of initial login.
(3) Fetch and return authentication date and time from authentication successful event entity.
Create a class that inherits User and a class that implements UserDetailsService as shown below
to fetch the date and time of previous login and retain it in UserDetails at the time of login.
package org.terasoluna.securelogin.domain.service.userdetails;
// omitted
// omitted
(1) Declare a field to retain the date and time of previous login.
(2) Set the date and time of previous login assigned as an argument in the field.
(3) A method to return the retained date and time of previous login
package org.terasoluna.securelogin.domain.service.userdetails;
// omitted
@Service
public class LoggedInUserDetailsService implements UserDetailsService {
@Inject
AccountSharedService accountSharedService;
@Transactional(readOnly = true)
@Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
try {
Account account = accountSharedService.findOne(username);
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : account.getRoles()) {
authorities.add(new SimpleGrantedAuthority("ROLE_"
+ role.getRoleValue()));
}
return new LoggedInUser(account,
accountSharedService.isLocked(username),
accountSharedService.getLastLoginDate(username), // (1)
authorities);
} catch (ResourceNotFoundException e) {
throw new UsernameNotFoundException("user not found", e);
}
}
(1) Fetch date and time of previous login by calling Service method and pass it to constructor of
LoggedInUser .
Implement application layer to display date and time of previous login on the top screen.
package org.terasoluna.securelogin.app.welcome;
// omitted
@Controller
public class HomeController {
@Inject
AccountSharedService accountSharedService;
// omitted
return "welcome/home";
(3) Format date and time of last login, set it in Model and pass to View.
Top screen(home.jsp)
<body>
<div id="wrapper">
</div>
</body>
(2) Display the date and time of previous login passed from the Controller.
Working image
Enter the user name for which password is to be reissued on the screen to generate authentication information
for password reissue. At this time, the confidential information and token to be used for authentication during
password reissue, are generated. Confidential information is displayed on the screen and the URL for password
reissue screen containing the token is sent to the registered e-mail address of the user.
There is an expiry date to the URL sent by e-mail. Password can be changed by accessing the URL within the
expiry date and entering the confidential information and the new password. If the URL sent by e-mail is accessed
after the expiry date, the user is taken to the error screen.
Confidential information and token generation is described from the flow mentioned above.
Implementation method
While reissuing the password, an alternative to password is required to verify that the user is the owner of the
account.
In this application, URL of password reissue screen and confidential information are used as the information to
verify the user.
Create a random string and add it to URL to make the password reissue screen URL unique and difficult to guess.
Create confidential information which is in the form of a random string and use it for authentication as a measure
against accidental leakage of URL.
Create two random strings by different ways so that that it becomes impossible to guess a string from the other
string.
In particular, fulfil the requirements by implementing the following process.
Store the following information as the authentication information for password reissue in the database.
– User name: User name of the account for which password is to be reissued
– Token: Random string generated to make the password reissue screen URL unique and difficult to
guess
– Confidential information: Random string generated for user input at the time of password reissue
– Expiry date: Expiry date for authentication information for password reissue
Use randomUUID method of java.util.UUID class for token generation and Password generation
function of Passay for generating confidential information.
Save the confidential information to the database by hashing similar to password. Expiry date settings
and confirmation process are described in Validation at the time of executing password reissue. Refer
to Distribute authentication information for password reissue for the method to distribute authentication
information for password reissue to the user.
Code description
• Common part
In the implementation according to the implementation method mentioned above, the process to regis-
ter and search the authentication information for password reissue in the database is commonly required.
Therefore, implementation of Entity and Repository related to the authentication information for password
reissue is described first.
– Creation of Entity
package org.terasoluna.securelogin.domain.model;
// omitted
@Data
public class PasswordReissueInfo {
(2) String that is generated to be included in the URL for password reissue (Token)
(3) String to verify the user at the time of password reissue (Confidential information)
– Implementation of Repository
Repository to search, register and delete the authentication information for password reissue is shown
below.
package org.terasoluna.securelogin.domain.repository.passwordreissue;
// omitted
// omitted
(2) A method to search and fetch PasswordReissueInfo object by considering the token
assigned as an argument, as the key
<mapper
namespace="org.terasoluna.securelogin.domain.repository.passwordreissue.PasswordReiss
username,
token,
secret,
expiry_date
) VALUES (
#{username},
#{token},
#{secret},
#{expiryDate}
)
]]>
</insert>
<delete id="delete">
<![CDATA[
DELETE FROM
password_reissue_info
WHERE
token = #{token}
]]>
</delete>
</mapper>
The definition of password generator and generation rules to use the password generation function of
Passay is shown below. Refer to Password generation for the password generator and generation rules.
(1) Define a Bean for password generator to be used in password generation function of Passay
(2) Define a Bean for password generation rules to be used in password generation function of
Passay. Using the validation rules that were used in Check password strength, define
generation rules for the password containing one or more characters of single-byte upper
case letters, single-byte lower case letters and single-byte digits respectively.
applicationContext.xml
– Implementation of Service
The implementation of the process to create authentication information for password reissue and store
in the database is shown below. The authentication information generated in this process is sent by
e-mail. Sending the information by e-mail is omitted here as it is described later.
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
@Inject
AccountSharedService accountSharedService;
@Inject
PasswordEncoder passwordEncoder;
@Inject
PasswordGenerator passwordGenerator; // (1)
@Resource(name = "passwordGenerationRules")
List<CharacterRule> passwordGenerationRules; //(2)
@Value("${security.tokenLifeTimeSeconds}")
int tokenLifeTimeSeconds; // (3)
// omitted
@Override
public String createAndSendReissueInfo(String username) {
if(!accountSharedService.exists(username)){ // (5)
return rowSecret;
passwordReissueInfoRepository.create(info); // (11)
// omitted
(2) Inject password generation rules to be used in password generation function of Passay.
(3) Specify the length of the period for which authentication information for password reissue
is valid, in seconds. The value defined in the property file is injected.
(4) Create a random string of length 10 in accordance with the password generation rules using
the password generation function of Passay to use as confidential information.
(5) Check whether the account of user name that is passed as an argument, exists. If it does not
exist, return dummy confidential information since non-existence of the user is not known.
(6) Fetch the account information of the user name included in the authentication information
for password reissue.
(7) Create a random string using randomUUID method of java.util.UUID class to use as
a token.
(8) By adding the value of (3) to current time, calculate expiry date for the authentication
information for password reissue.
(9) Create authentication information for password reissue and set the user name, token,
confidential information and expiry date.
– Implementation of Form
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Data
public class CreateReissueInfoForm implements Serializable {
@NotEmpty
private String username;
}
– Implementation of View
<body>
<div id="wrapper">
<h1>Reissue password</h1>
<t:messagesPanel />
<form:form
action="${f:h(pageContext.request.contextPath)}/reissue/create"
method="POST" modelAttribute="createReissueInfoForm">
<table>
<tr>
<th><form:label path="username" cssErrorClass="error-label">Username<
</th>
<td><form:input path="username" cssErrorClass="error-input" /></td>
<td><form:errors path="username" cssClass="error-messages" /></td>
</tr>
</table>
– Implementation of Controller
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Controller
@RequestMapping("/reissue")
@Inject
PasswordReissueService passwordReissueService;
// omitted
(1) Create authentication information for password reissue from the user name fetched from
Form and call the process registered in the database.
• Separate distribution for password reissue screen URL and confidential information
Working image
Creation of authentication information for password reissue is described in Creating authentication information
for password reissue. The distribution of the created authentication information is described here.
Perform the authentication for password reissue using the password reissue screen URL and confidential informa-
tion. Distribute the information to the user using different methods to prevent the leakage of the information at the
same time. In this application, URL of the password reissue screen is sent to the registered e-mail address of the
user, and confidential information is displayed on the screen.
Implementation method
Split the authentication information created using Create authentication information for password reissue and
distribute to the user using separate methods.
Fulfil the requirements by implementing and using the following two processes.
Distribute the confidential information before hashing that is created using Create authentication informa-
tion for password reissue to the user by displaying it on the screen.
Send the URL of password reissue screen including token that is created using Create authentication infor-
mation for password reissue by e-mail using the component for Spring Framework Mail linkage.
Code description
The code implemented according to the above implementation method is described sequentially.
A series of implementations to call the process to create confidential information from the Controller and
display it in View is shown below.
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Controller
@RequestMapping("/reissue")
public class PasswordReissueController {
@Inject
PasswordReissueService passwordReissueService;
// omitted
// omitted
(2) Using RedirectAttributes, pass the confidential information to the redirect destination.
<body>
<div id="wrapper">
<h1>Your Password Reissue URL was successfully generated.</h1>
The URL was sent to your registered E-mail address.<br /> Please
access the URL and enter the secret shown below.
<h3>Secret : <span id=secret>${f:h(secret)}</span></h3> <!-- (1) -->
</div>
</body>
The implementation of the process to create URL of password reissue screen from the authentication infor-
mation for password reissue screen and send it by e-mail is shown below. Refer to Sending E-mail (SMTP)
for more information about how to add dependent libraries and how to fetch an e-mail session.
package org.terasoluna.securelogin.domain.service.mail;
// omitted
@Service
public class PasswordReissueMailSharedServiceImpl implements
PasswordReissueMailSharedService {
@Inject
@Inject
@Named("passwordReissueMessage")
SimpleMailMessage templateMessage; // (2)
// omitted
@Override
public void send(String to, String text) {
SimpleMailMessage message = new SimpleMailMessage(templateMessage); // (3)
message.setTo(to);
message.setText(text);
mailSender.send(message);
}
(3) Create an instance of SimpleMailMessage from the template, set the destination email
address and text assigned as arguments and send.
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordReissueMailSharedService mailSharedService;
@Inject
AccountSharedService accountSharedService;
@Inject
PasswordEncoder passwordEncoder;
@Value("${security.tokenLifeTimeSeconds}")
int tokenLifeTimeSeconds;
@Value("${app.applicationBaseUrl}") // (1)
String baseUrl;
@Value("${app.passwordReissueProtocol}")
String protocol;
// omitted
@Override
public String createAndSendReissueInfo(String username) {
if(!accountSharedService.exists(username)){
return rowSecret;
}
passwordReissueInfoRepository.create(info);
return rowSecret;
// omitted
(1) Fetch the base URL to be used in the password reissue screen URL from the property file.
(2) Using the value fetched in (1) and the token included in the created authentication information
for password reissue, create the URL of password reissue screen to be distributed to the user.
Use org.springframework.web.util.UriComponentsBuilder to create the
URL. UriComponentsBuilder is described in Implementing hypermedia link.
(3) Send an email with the URL of the password reissue screen mentioned in the mail text to the
registered e-mail address of the user.
Working image
The distribution of authentication information for password reissue is described in Distribution of authentication
information for password reissue. The process for using the distributed authentication information is described
below.
As the authentication at the time of password reissue, verify the confidential information and URL of password
reissue screen distributed separately in Distribution of authentication information for password reissue. Password
is reissued only if the set of token and confidential information that is included in the URL is correct.
Moreover, set expiry date to the authentication information so that it is not valid for a long period of time unnec-
essarily as generally password is reissued immediately after the creation of authentication information. When the
URL for password reissue screen is accessed, password reissue screen is displayed if the authentication informa-
tion is within the expiry date and transits to error screen after the expiry date.
Implementation method
Token is included as a request parameter in the URL for password reissue screen sent by e-mail. Fetch the token
when the password reissue screen is accessed and search the authentication information for password reissue
from the database by considering this token as the key.
At the time of creating authentication information, set the expiry date in advance and check for expiration when it
is obtained from the database. If it is within the expiry date, display the change password screen and accept the
input for confidential information and new password.
If confidential information in the authentication information obtained from the database and the confidential
information entered by the user are identical, then authentication is successful and password is reissued.
In particular, fulfil the requirements by implementing the following three processes.
Set expiry date to the authentication information created in the process described in Creating authentication
information for password reissue.
When the password reissue screen is accessed, fetch the token included in the request parameter and search
the authentication information for password reissue stored in the database considering the token as the key.
Compare the expiry date included in authentication information with current time and after expiry date,
transit to the error screen.
When reissuing the password, check whether the combination of user name, token and the confidential
information provided by the user and the authentication information in the database are identical. If they
are identical, reissue the password. If they are not identical, display the error message.
Code description
The settings for expiry date to the authentication information for password reissue are included in the
process described in Creating authentication information for password reissue. Here, only relevant imple-
mentation points are shown again.
– Implementation of Service
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
@Value("${security.tokenLifeTimeSeconds}")
int tokenLifeTimeSeconds; // (1)
// omitted
@Override
public String createAndSendReissueInfo(String username) {
// omitted
passwordReissueInfoRepository.create(info); // (4)
// omitted
(1) Specify the length of the period for which authentication information for password reissue
is valid, in seconds. The value defined in property file is injected.
(2) By adding the value of (1) to current time, calculate expiry date for the authentication
information for password reissue.
(3) Create authentication information for password reissue and set the user name, token,
confidential information and expiry date.
(4) Register the authentication information for password reissue in the database.
The implementation of the process to fetch authentication information for password reissue from the token
included in the URL as a request parameter and check whether it is within the expiry date when the pass-
word reissue screen is accessed, is shown below. In this process, it is also checked whether the maximum
limit for password reissue failure has exceeded. However, it is omitted here and will be described later.
– Implementation of Service
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
ClassicDateFactory dateFactory;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
// omitted
@Override
@Transactional(readOnly = true)
public PasswordReissueInfo findOne(String token) {
PasswordReissueInfo info = passwordReissueInfoRepository.findOne(token); // (1)
if (info == null) {
throw new ResourceNotFoundException(ResultMessages.error().add(
MessageKeys.E_SL_PR_5002, token));
}
if (dateFactory.newTimestamp().toLocalDateTime()
.isAfter(info.getExpiryDate())) { // (2)
throw new BusinessException(ResultMessages.error().add(
MessageKeys.E_SL_PR_2001));
}
return info;
}
// omitted
(1) Fetch authentication information for password reissue from the database by considering the
token assigned as an argument, as the key.
– Implementation of Controller
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Controller
@RequestMapping("/reissue")
@Inject
PasswordReissueService passwordReissueService;
// omitted
form.setUsername(info.getUsername());
form.setToken(token);
model.addAttribute("passwordResetForm", form);
return "passwordreissue/passwordResetForm";
}
// omitted
(1) Fetch the token included as a request parameter in the URL for password reissue screen.
(2) Call the Service method by passing the token in it. Authentication information is fetched
from the database and expiry date is checked.
The implementation of the process to confirm whether the set of confidential information entered by the
user on the password reissue screen and the token included in the URL of the password reissue screen is
correct, is shown below. This confirmation process is a password reissue-specific logic. Since it is a check
in which the results vary depending on the contents of the database, it is implemented in the Service without
using Bean Validation and Spring Validator.
– Implementation of Service
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
// omitted
// omitted
(1) A method to set the new password after user verification using user name, token and
confidential information assigned as arguments
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
PasswordReissueFailureSharedService passwordReissueFailureSharedService;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
@Inject
AccountSharedService accountSharedService;
@Inject
PasswordEncoder passwordEncoder;
// omitted
@Override
public boolean resetPassword(String username, String token, String secret,
String rawPassword) {
PasswordReissueInfo info = this.findOne(token); // (1)
if (!passwordEncoder.matches(secret, info.getSecret())) { // (2)
passwordReissueFailureSharedService.resetFailure(username, token);
throw new BusinessException(ResultMessages.error().add(
MessageKeys.E_SL_PR_5003));
}
failedPasswordReissueRepository.deleteByToken(token);
passwordReissueInfoRepository.delete(token); // (3)
// omitted
(1) Using the token assigned as an argument, fetch the authentication information for password
reissue from the database. At this time, the expiry date is checked again.
(2) Compare the hashed confidential information included in the authentication information for
password reissue with the confidential information given as an argument. If they vary,
throw BusinessException . Password reissue fails in this case.
(3) Delete used authentication information from the database in order to prevent it from being
reused.
(4) Update the account password that has user name that was passed as an argument to the
specified new password.
– Implementation of Form
Since input check other than Null check is covered depending on the annotation assigned to the class,
only @NotNull is assigned as a single item check.
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Data
@Compare(source = "newPasssword", destination = "confirmNewPassword", operator = Compare.
@StrongPassword(usernamePropertyName = "username", newPasswordPropertyName = "newPassword
@NotReusedPassword(usernamePropertyName = "username", newPasswordPropertyName = "newPassw
public class PasswordResetForm implements Serializable{
@NotNull
private String username;
@NotNull
private String token;
@NotNull
private String secret;
@NotNull
private String newPassword;
@NotNull
private String confirmNewPassword;
}
(1) An annotation to check the password strength. Refer to Check password strength for details.
(2) An annotation to check reuse of password. Refer to Check password strength for details.
– Implementation of View
<body>
<div id="wrapper">
<h1>Reset Password</h1>
<t:messagesPanel />
<form:form
action="${f:h(pageContext.request.contextPath)}/reissue/resetpassword"
method="POST" modelAttribute="passwordResetForm">
<table>
<tr>
<th><form:label path="username">Username</form:label></th>
<td>${f:h(passwordResetForm.username)} <form:hidden
path="username" value="${f:h(passwordResetForm.username)}" />
</td>
<td></td>
</tr>
<form:hidden path="token" value="${f:h(passwordResetForm.token)}" /> <!--
<tr>
<th><form:label path="secret" cssErrorClass="error-label">Secret</for
</th>
<td><form:password path="secret" cssErrorClass="error-input" /></td>
<td><form:errors path="secret" cssClass="error-messages" /></td>
</tr>
<tr>
<th><form:label path="newPassword" cssErrorClass="error-label">New pa
</th>
<td><form:password path="newPassword"
cssErrorClass="error-input" /></td>
<td><form:errors path="newPassword" cssClass="error-messages"
htmlEscape="false" /></td>
</tr>
<tr>
<th><form:label path="confirmNewPassword"
cssErrorClass="error-label">New password(Confirm)</form:label
<td><form:password path="confirmNewPassword"
cssErrorClass="error-input" /></td>
<td><form:errors path="confirmNewPassword"
cssClass="error-messages" /></td>
</tr>
</table>
(3) Prompt the user to enter confidential information for user verification.
<body>
<div id="wrapper">
<h1>Your password was successfully reset.</h1>
<a href="${f:h(pageContext.request.contextPath)}/">go to Top</a>
</div>
</body>
– Implementation of Controller
package org.terasoluna.securelogin.app.passwordreissue;
// omitted
@Controller
@RequestMapping("/reissue")
public class PasswordReissueController {
@Inject
PasswordReissueService passwordReissueService;
// omitted
try {
passwordReissueService.resetPassword(form.getUsername(),
form.getToken(), form.getSecret(), form.getNewPassword()); // (1)
return "redirect:/reissue/resetpassword?complete";
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return showPasswordResetForm(form, model, form.getToken());
}
}
// omitted
(1) Pass the user name, token, confidential information and new password to the method of
Service. If the combination of user name, token and confidential information is correct, it is
updated to the new password.
Working image
Even if URL for password reissue screen is leaked for some reason, password will not be reissued illegally if
the confidential information is not leaked. Since a random value which cannot be guessed easily is used in the
confidential information, it is highly unlikely to break the information easily. However, a maximum limit is set for
the number of authentication failures to prevent brute force attack. When the authentication failures for password
reissue exceeds the maximum limit, the password reissue for that URL (token) is disabled.
Implementation method
In this application, history of password reissue failure is stored in the database as “password reissue failure event”
entity and number of failures for password reissue is measured by using the password reissue failure event entity.
When the number of failures is greater than the maximum value set in advance, an exception is thrown when the
user tries to access password reissue screen.
In particular, fulfil the requirements by implementing and using following two processes.
When a failure occurs in the user authentication, during the process “Confirmation of user using authenti-
cation information for password reissue” in Validation at the time of executing password reissue, set of the
token used and failure date and time are registered in the database as password reissue failure event entity.
When authentication information is fetched from the database for password reissue, number of password
reissue failure event entities is measured and an exception is thrown if the number is more than the maxi-
mum limit.
Warning: Since password reissue failure event entity is only intended for measuring number of failures for
password reissue, it is deleted when it is no longer required. When the log at the time of password reissue
failure is required, a separate log must always be maintained.
Code description
• Common parts
As a prerequisite, each process mentioned in Validation at the time of executing password reissue should
be implemented. Other commonly required implementations related to registration, search and deletion of
password reissue failure event entity for the database are shown below.
– Implementation of Entity
package org.terasoluna.securelogin.domain.model;
// omitted
@Data
public class FailedPasswordReissue {
– Implementation of Repository
package org.terasoluna.securelogin.domain.repository.passwordreissue;
// omitted
// omitted
<mapper
namespace="org.terasoluna.securelogin.domain.repository.passwordreissue.FailedPasswordRe
]]>
</select>
<delete id="deleteByToken">
<![CDATA[
DELETE FROM
failed_password_reissue
WHERE
token = #{token}
]]>
</delete>
</mapper>
Code implemented in accordance with the implementation method is described here sequentially.
A class which implements the process to be carried out at the time of password reissue failure is shown
below.
package org.terasoluna.securelogin.domain.service.passwordreissue;
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueFailureSharedServiceImpl implements
PasswordReissueFailureSharedService {
@Inject
ClassicDateFactory dateFactory;
@Inject
FailedPasswordReissueRepository failedPasswordReissueRepository;
// omitted
(1) It is a method which is called when a failure occurs during password reissue and has been
designed to generate a run-time exception in the call source.
Therefore, specify a propagation method in “REQUIRES_NEW” in order to perform
transaction management separately from that of call source service.
(2) Create password reissue failure event entity and specify token and, failure date and time.
(3) Register password reissue failure event entity created in (2), in database.
Call process at the time of password reissue failure from “Confirmation of user using authentication infor-
mation for password reissue” process of Validation at the time of executing password reissue.
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
PasswordReissueFailureSharedService passwordReissueFailureSharedService;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
@Inject
AccountSharedService accountSharedService;
@Inject
PasswordEncoder passwordEncoder;
// omitted
@Override
public boolean resetPassword(String username, String token, String secret,
String rawPassword) {
PasswordReissueInfo info = this.findOne(token); // (1)
if (!passwordEncoder.matches(secret, info.getSecret())) { // (2)
passwordReissueFailureSharedService.resetFailure(username, token); // (3)
throw new BusinessException(ResultMessages.error().add( // (4)
MessageKeys.E_SL_PR_5003));
}
//omitted
// omitted
(1) Fetch authentication information for password reissue from the database, using token assigned
as an argument.
(2) Compare hashed confidential information included in the authentication information for
password reissue and confidential information assigned as an argument.
(3) Call a method of SharedService which performs a process at the time of password reissue
failure.
(4) A run-time exception is thrown, however since the process at the time of password reissue
failure is performed in different transaction, it does not leave any impact.
Fetching number of failures at the time of password reissue and implementation of process when the number
of failures reach the maximum limit are shown below.
package org.terasoluna.securelogin.domain.service.passwordreissue;
// omitted
@Service
@Transactional
public class PasswordReissueServiceImpl implements PasswordReissueService {
@Inject
FailedPasswordReissueRepository failedPasswordReissueRepository;
@Inject
PasswordReissueInfoRepository passwordReissueInfoRepository;
@Value("${security.tokenValidityThreshold}")
int tokenValidityThreshold; // (1)
// omitted
@Override
@Transactional(readOnly = true)
public PasswordReissueInfo findOne(String token) {
// omitted
return info;
}
// omitted
(1) Fetch and set maximum value for number of failures at the time of password reissue, from
property file.
(2) Fetch number of password reissue failure event entities from the database, considering token
assigned as an argument, as a key.
(3) Compare number of failure event entities at the time of password reissue that has been fetched
and maximum limit for number of failures, and throw an exception if it exceeds the maximum
limit.
Working image
Implementation method
Since the scope for performing a check greatly vary for the configuration of common prohibited characters of
overall application and individual input checks, implementation is carried out separately.
For configuration of common prohibited strings, two methods described in Implementing common logic to be
executed before and after calling controller can be used. In this application, implementation is carried out by
using Servlet Filter in order to perform check regardless of whether mapping is done in the handler method of
Controller. When an input error occurs, it signifies that the input values which were not predicted as the results of
normal user operation are input and are described in the configuration file so as to transit to a common error
screen without considering reduction in usability.
For individual input check, Input Validation function can be used. Input value check is performed in this
application using Bean Validation. For the individual input error, implementation is done so as to encourage
re-input by displaying an error message for input items corresponding to Bean Validation.
Code description
Code implemented in accordance with the implementation method above is explained sequentially.
When the user input is to be used in the application, it may serve as a target for injection attacks like SQL
injection and XSS directory traversal (path traversal).
An implementation example of Servlet Filter to validate the input from the user in overall application is
shown as a countermeasure for these attacks.
In this application, it is verified that prohibited characters respectively configured for following items are
not included.
(1) Request parameters Since the request parameters are generally used for receiving the input
from the user, the parameters act as a target for input check.
Since both the parameter name and the value are entered by user, both the
name and the value are checked.
(2) Upload file name Since file upload function is executed in this application, the file name of
uploaded file entered by user acts as a target for input check.
package org.terasoluna.securelogin.app.common.filter;
// omitted
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request != null) {
validateRequestParams(request); // (4)
.getName(); // (9)
validate(filename, prohibitedCharsForFileName); // (10)
}
}
(1) It is guaranteed that the process is executed only once for the request by inheriting
org.springframework.web.filter.OncePerRequestFilter .
(2) Receive the list of prohibited characters of request parameters as a string and store as a list of
characters.
(3) Receive the list of prohibited characters of file name as a string and store as a list of characters.
(6) After completing input check, call a method to execute subsequent servlet filter process.
(7), (8) Fetch the list of request parameters from HttpServletRequest and call a method to
perform actual input check for each request parameter name and request parameter value.
(9) Fetch the list of files uploaded from MultipartRequest and fetch actual file name.
Since path is included in the file name according to browser or OS of the client and path
delimiter tends to vary, the process is required to fetch only file name.
(10) Call a method to perform actual input check for the file name of each uploaded file.
(11) Check
9.9. Implementation whether
Example ofthe string for
Typical input check
Security is included in the prohibited characters - sequentially
Requirements 1993
one character at a time and throw an exception if the prohibited character is included
InvalidCharacterException is an exception created by inheriting
RuntimeException . Code is omitted.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Tip: In this application, only request parameters and file names are checked for common prohibited
characters. If required, similar check can also be implemented for HTTP header or cookies by fetching the
value of HTTP header and Cookie using getHeaders and getCookies of HttpServletRequest
.
web.xml
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class> <
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>inputValidationFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- (
</filter>
<filter-mapping>
<filter-name>inputValidationFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
<exception-type>org.terasoluna.securelogin.app.common.filter.exception.InvalidCharacterEx
<location>/WEB-INF/views/common/error/invalidCharacterError.jsp</location>
</error-page>
Note: Since MultipartFilter is used for input check of file name, configuration to validate upload
function of Servlet 3.0 described in Application settings is required in addition to the details described here.
invalidCharacterError.jsp
applicationContext.xml
(1) Fetch the list of prohibited characters of request parameter from property, as a string
(2) Fetch the list of prohibited characters of file name from property, as a string
application.properties
## (1)
app.security.prohibitedChars=&\\!"<>*
## (2)
app.security.prohibitedCharsForFileName=&\\!"<>*;:
(1) In this application, specify a list of characters which are not intended to be included in the
request parameters, as a string
(2) In this application, specify a list of characters which are not intended to be included in the
uploaded file name, as a string
An annotation of Bean Validation which validates the input value based on the requirements is created
to alleviate the security risk posed by the input value which was not considered in the specifications of
application.
When the control characters are included in the input value, an unexpected issue is likely to occur in
the application. Hence, it is checked that the control characters are not included for the input items
which do not require input of control characters.
Since only linefeed character of control characters is allowed in some cases at the time of text input,
an annotation to allow linefeed code is created separately.
It can be verified that control characters are not included by performing a check using normal
expressions.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = {})
@Target({ FIELD })
@Retention(RUNTIME)
@ReportAsSingleViolation // (1)
@Pattern(regexp = "^\\P{Cntrl}*$") // (2)
public @interface NotContainControlChars {
String message() default "{org.terasoluna.securelogin.app.common.validation.NotContai
@Target({ FIELD })
@Retention(RUNTIME)
@Documented
public @interface List {
NotContainControlChars[] value();
}
(2) Assign @Pattern annotation to perform input check using normal expressions
Since \P{Cntrl} signifies “characters other than control characters” in the normal
expressions of Java, ^\\P{Cntrl}*$ matches only with the string which does not
include a control character from beginning to end
Similarly, implementation example for the annotation which allows linefeed code is shown below.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = {})
@Target({ FIELD })
@Retention(RUNTIME)
@ReportAsSingleViolation
@Pattern(regexp = "^[\\r\\n\\P{Cntrl}]*$") // (1)
public @interface NotContainControlCharsExceptNewlines {
String message() default "{org.terasoluna.securelogin.app.common.validation.NotContai
@Target({ FIELD })
@Retention(RUNTIME)
@Documented
public @interface List {
NotContainControlCharsExceptNewlines[] value();
}
(1) Specify normal expressions to match the string consisting of only “characters other than
control character (\P{Cntrl} )” and linefeed code (\r , \n ).
When a file uploaded by the user is to be received, the format of the file to be received is likely to be
restricted. In such cases, a list of extensions for the files allowed is set and it is checked whether the
extension of the uploaded file is included in the list.
It is a specification which can be changed for whether the upper case and lower case extensions are to
be differentiated.
Warning: Since extension of the file can be disguised easily, a file format should not be trusted
unconditionally even after performing the extension check.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = { FileExtensionValidator.class })
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface FileExtension {
String message() default "{org.terasoluna.securelogin.app.common.validation.FileExten
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
FileExtension[] value();
}
}
(2) Whether to ignore the differentiation between uppercase and lowercase characters. Default
value is true (ignore)
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Override
public void initialize(FileExtension constraintAnnotation) {
this.extensions = new HashSet<String>(
Arrays.asList(constraintAnnotation.extensions()));
this.ignoreCase = constraintAnnotation.ignoreCase();
}
@Override
public boolean isValid(MultipartFile value,
ConstraintValidatorContext context) {
if (value == null) { // (1)
return true;
(1) Return true in case of null since null check is performed by using another annotation.
(2) Fetch extension from the file name by using getFilenameExtension method of
org.springframework.util.StringUtils
(3) Return false for the file without an extension since it is not permissible.
(4) Fetch extensions one by one from the list of extensions to be allowed and compare with the
string fetched from the file name.
Return true if even one of the extensions show a match and return false if it does not
match with any of the extensions in the list.
– Annotation that verifies that file name of the uploaded file matches with the patterns that are allowed
When a file uploaded by the user is to be received, the format of the file to be received is likely to be
restricted. In such cases, pattern for the file name to be allowed is set in normal expression and it is
checked that file name of uploaded file matches with the pattern.
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = { FileNamePatternValidator.class })
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface FileNamePattern {
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface List {
FileNamePattern[] value();
}
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Override
public void initialize(FileNamePattern constraintAnnotation) {
this.pattern = Pattern.compile(constraintAnnotation.pattern()); // (1)
}
@Override
public boolean isValid(MultipartFile value,
ConstraintValidatorContext context) {
if (value == null) { // (2)
return true;
}
(2) Return true in case of null since null check is performed by using another annotation.
(3) Fetch actual file name from MultipartRequest . Since path is included in the file
name in accordance with browser and OS of client and path delimiter varies, the process is
required for fetching only file name.
(4) Return true when Pattern created in (1) and file name match otherwise return false .
When a URL input by the user is to be received, the domain that are allowed are likely to be
restricted. In such cases, list of domains allowed is set and it is checked whether the domain of input
URL is a domain included in the list or is a subdomain.
Since URL format is checked at the same time, implementation is done combining with
org.hibernate.validator.constraints.URL .
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = { DomainRestrictedURLValidator.class })
@Target({ FIELD })
@Retention(RUNTIME)
@URL // (1)
public @interface DomainRestrictedURL {
@Target({ FIELD })
@Retention(RUNTIME)
@Documented
public @interface List {
DomainRestrictedURL[] value();
}
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Override
public void initialize(DomainRestrictedURL constraintAnnotation) {
allowedDomains = new HashSet<String>(Arrays.asList(constraintAnnotation
.allowedDomains())); // (2)
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
(1) Create and retain a pattern of normal expressions as a Pattern for fetching URL domain
Since whether the URL format is correct is verified by @URL , only minimum required
expressions are specified here.
(3) Check whether the URL format is correct. If the format is invalid, return true here since a
validation error occurs in URL annotation used in combination
(4) Fetch the domain one by one from the list of domains to be allowed and check whether it
matches with the end part of host name of URL. Return true if even one of the values
match and return false if it does not match with any of the values
When a mail address input by the user is to be received, the domains that are allowed are likely to be
restricted. In such cases, list of allowed domains is set and it is checked that domain of input mail
address is included in the list. It is a specification which can be changed to include whether to allow
the subdomain of domain.
Since the mail address format is checked at the same time, implementation is done combining with
org.hibernate.validator.constraints.Email .
package org.terasoluna.securelogin.app.common.validation;
// omitted
@Documented
@Constraint(validatedBy = { DomainRestrictedEmailValidator.class })
@Target({ FIELD })
@Retention(RUNTIME)
@Email // (1)
public @interface DomainRestrictedEmail {
String message() default "{org.terasoluna.securelogin.app.common.validation.DomainRes
@Target({ FIELD })
@Retention(RUNTIME)
@Documented
public @interface List {
DomainRestrictedEmail[] value();
}
(3) Whether to allow a subdomain. Default value is false (do not allow)
package org.terasoluna.securelogin.app.common.validation;
// omitted
ConstraintValidator<DomainRestrictedEmail, CharSequence> {
@Override
public void initialize(DomainRestrictedEmail constraintAnnotation) {
allowedDomains = new HashSet<String>(Arrays.asList(constraintAnnotation
.allowedDomains())); // (1)
allowSubDomain = constraintAnnotation.allowSubDomain(); // (2)
}
@Override
public boolean isValid(CharSequence value,
ConstraintValidatorContext context) {
if (value == null) { // (3)
return true;
}
(2) Fetch and retain the truth value which indicates whether the subdomain is allowed
(3) Return true in case of null since null check is performed by another annotation
(4) Fetch domain one by one from the list of domains to be allowed and check whether it
matches with the domain part of the mail address. Return true even if one of the domains
show a match and return false of it does not match any of the domains.
Annotation created is assigned to the field of Form class of new account creation.
package org.terasoluna.securelogin.app.account;
// omitted
// omitted
@NotNull
@NotContainControlChars // (1)
@Size(min=4, max=128)
private String username;
// omitted
@NotNull
@NotContainControlChars
@Size(min=1, max=128)
@DomainRestrictedEmail(allowedDomains={ "domainexample.co.jp",
"somedomainexample.co.jp" }, allowSubDomain=true) // (2)
private String email;
// omitted
@NotNull
@NotContainControlChars
@UploadFileRequired
@UploadFileNotEmpty
@UploadFileMaxSize
@FileExtension(extensions = { "jpg", "png", "gif" }) // (4)
@FileNamePattern(pattern = "[a-zA-Z0-9_-]+\\.[a-zA-Z]{3}") // (5)
private MultipartFile image;
@NotNull
@NotContainControlCharsExceptNewlines // (6)
private String profile;
(2) Check that the mail address domain is “ntt.co.jp”, “nttdata.co.jp” or its subdomain
(4) Check that file extemsions are one of the “jpg”, “png” and “gif” extensions
(5) Check that the file name is of “repeating 1 or more characters of Alphanumeric, “_”, “-” ” + ”
”.” ” + “single byte alphabet 3 characters” pattern
(6) Check that control characters other than linefeed character are not included
Working image
While calling a service class method, date and time of calling, name of the user calling and method name are
output in a log for the audit in order to check the information regarding the application like the user who has
performed the operation, date and time at which the operation is performed, what kind of operation is performed
etc. Further, for the results of method execution, log is output as “operation successful” if exception does not
occur during the operation and “operation failed” if exception occurs during the process.
Date and time of method calling Date and time at which service class method is called is output in
“yyyy-MM-dd HH:mm:ss” format
Name of user calling Output user name of Spring Security which calls service class method
Kept blank in case of no login
How to implement
User name is fetched from authentication information of Spring Security in order to output user name in the log.
Fetching user name and log output are implemented by using
org.terasoluna.gfw.security.web.logging.UserIdMDCPutFilter described in Logging .
Further, operation details and operation results for the request are defined as below and output in a log, for this
application.
• Operation details: Name of the method of service class that has been called
• Operation results: Whether the exceptions have occurred in the results of method processing
AOP(Aspect Oriented Programming) function offered by Spring can be used to achieve a cross-sectional function
like log output for calling of all the methods of service class
AOP offered by Spring offers many implementation methods. This application puts an emphasis on combining
with implementation of logging related components offered by common library
<https://github.com/terasolunaorg/terasoluna-gfw>‘_ and adopts a method to implement
org.aopalliance.intercept.MethodInterceptor .
Requirements are fulfilled by implementation and configuration given below.
• Configure UserIdMDCPutFilter
• Create an advice which outputs a log at the time of calling a method and after execution
• Configure to apply the advice defined above for the class assigned with @Service
Note: Advice indicates a process which is executed for AOP within specified timing. Further, the
place where advice can be embedded is called a joining point whereas set of join points where advice
is embedded is called as a point cut For AOP function offered by Spring, refer Official document - AOP
<http://docs.spring.io/spring/docs/4.2.7.RELEASE/spring-framework-reference/html/aop.html>‘_ .
Code description
Code implemented in accordance with the implementation method above is sequentially explained.
• Configure UserIdMDCPutFilter
Configuration to output authenticated user name of Spring Security in log is shown below.
spring-security.xml
<sec:http>
<!-- omitted -->
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER" />
<!-- omitted -->
</sec:http>
logback.xml
<appender name="AUDIT_LOG_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- (1) -->
<file>log/security-audit.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>log/security-audit-%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern><![CDATA[date:%d{yyyy-MM-dd HH:mm:ss}\tthread:%thread\tUSER:%X{USER}\tX-Trac
</encoder>
</appender>
<logger
name="org.terasoluna.securelogin.domain.common.interceptor.ServiceCallLoggingInterceptor"
additivity="false"> <!-- (3) -->
<level value="info" />
<appender-ref ref="AUDIT_LOG_FILE" />
</logger>
• Create an advice which outputs a log at the time of calling a method and after execution
Implementation and configuration to output a log for operation details and operation results are shown
below.
package org.terasoluna.securelogin.domain.common.interceptor;
// omitted
@Override
public Object invoke(MethodInvocation invocation) throws Throwable { // (2)
String methodName = invocation.getMethod().getName();
String className = invocation.getMethod().getDeclaringClass()
.getSimpleName();
logger.info("[START SERVICE]{}.{}", className, methodName); // (3)
try {
Object result = invocation.proceed(); // (4)
logger.info("[COMPLETE SERVICE]{}.{}", className, methodName); // (5)
return result; // (6)
} catch (Throwable e) {
logger.info("[SERVICE THROWS EXCEPTION]{}.{}", className, // (7)
methodName);
logger.info(Exception : {}, Message : {}, e.getClass().getName(),
e.getMessage()); // (8)
throw e; // (9)
}
}
}
(1) Implement MethodInterceptor to describe the processes to be carried out before and after
calling a method
(3) Output name of the method to be called in a log before calling a method
(5) Output a “operation successful” log message if an exception does not occur in the results of
calling a method
(7) Output a “operation failed” message when an exception occurs in the results of calling a method
Tip: Detailed contents can also be output by using arguments at the time of calling a method, to further
extend the example of this application. However, in such a case, a password without hashing is likely to be
secure-login-domain.xml
<bean id="serviceCallLoggingInterceptor"
class="org.terasoluna.securelogin.domain.common.interceptor.ServiceCallLoggingInterceptor
<aop:config>
<aop:advisor advice-ref="serviceCallLoggingInterceptor"
pointcut="@within(org.springframework.stereotype.Service)" /> <!-- (2) -->
</aop:config>
(1) Define a Bean for advice class created above (implementation class of
MethodInterceptor )
(2) Set Bean which implements the advice, in advice-ref attribute of aop:advisor tag and
set point cuts in pointcut attribute respectively.
By defining a point cut @within(org.springframework.stereotype.Service) ,
the method calling of the class assigned with @Service is used as a target for advice.
Note: Spring AOP adopts a proxy method wherein a proxy class which has been auto-created handles
the method calling. Note that, as a constraint of AOP of Proxy method, advice is not executed for the
method calling of visibility other than public or the method calling within the same class. For details,
refer Official document .
Log at the time occurrence of exception is as below. Since the log is for the process performed before login,
the user name is not displayed.
9.9.4 Conclusion
This chapter explains about an example of implementation method for security measures using a sample
application.
Since it is likely that implementation method in this application cannot be used as it is in the actual development,
a different customised method must be considered according to the requirements, using details of this chapter as
a reference.
9.9.5 Appendix
Passay
Passay is a library which offers a password validation function and a password generation function. Passay API
consists of following three key components.
• Validation rules
It defines the conditions which must be fulfilled by password. Validation rules which are used widely like
length of the password and characters types to be included can be created easily by using the class offered
by library. Besides, the necessary validation rules can also be defined by the user.
• Validator
A component which performs password validation based on validation rules. Various validation rules can
be specified in a single validator.
• Generator
A component which generates a password in conformance with the validation rules for the assigned char-
acter type.
<dependencies>
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.1.0</version>
</dependency>
<dependencies>
Password validation
Overview
Some of the classes of validation rules offered by Passay are shown in the table below.
A class of validation rules which specify the rules: List of validation rules related to
CharacterCharacteristicsRule
number of rules that should be fulfilled, from character types (List<CharacterRule>
multiple CharacterRule . ). Specify in setter.
numberOfCharacteristics :
Minimum number of rules that should be
fulfilled (int ). Specify in setter.
UsernameRule A class of validation rules to check that matchBackwards : Also check whether
password should not contain the user name. the user name has been used in the reverse
(boolean ). Specify in constructor or setter.
ignoreCase : Not case-sensitive
(boolean ). Specify in constructor or setter.
In addition, classes of validation rules to check whether a specific character is to be included or to check using a
regular expression are also provided. For details, refer http://www.passay.org/.
How to use A validator can be created by passing the list of org.passay.Rule instance to the constructor
of PasswordValidator . DI can be applied by defining a Bean for validator as below which specifies the
validation rules. Note that, when a Bean is to be defined for multiple validation rules, DI must be applied using a
Bean name, by combining @Inject and @Named .
(1) Define a Bean for validation rules for specifying character type that
must be included in the password and minimum number of
characters for the character type.
(6) Define a Bean for validator. Pass a list of validation rules to the
constructor.
@Inject
PasswordValidator characterPasswordValidator;
// omitted
(1) Pass the password to be validated to the constructor of PasswordData and create an instance.
(3) Fetch password validation results in truth value by using isValid method of RuleResult .
Password generation
Overview
Password generator and generation rules are used in the password generation function for Passay. A generator is
an instance of org.passay.PasswordGenerator and the generation rules is a list of validation rules
related to character type (org.passay.CharacterRule ).
By assigning the length of the password to be generated and generation rules to the method of generator as
arguments, a password which fulfils the generation rules is generated.
How to use A method of creation of validation rules related to character type which is included in the generation
rules is similar to Password validation. DI can be applied by defining a bean for generation rules and generator as
given below.
(1) Define a Bean for validation rules for specifying the character type that should be included in the
password, and minimum number of characters for the character type.
(3) Specify number of characters. Since “1” is passed, validation rules are set to check whether one or
more single byte uppercase characters are included.
(7) Define a bean for generation rules. It is defined as a list of validation rules related to character type,
defined in (1)-(5).
@Inject
PasswordGenerator passwordGenerator;
@Resource(name = "passwordGenerationRules")
List<CharacterRule> passwordGenerationRules;
// omitted
(1) If length of the password to be generated and the generation rules are passed to
generatePassword method of PasswordGenerator as arguments, a password which fulfils
the generation rules is created.
Tip: When DI is to be applied to a collection for which a Bean is defined, an expected operation is not performed
by @Inject + @Named . Therefore, DI is applied by Bean name using @Resource instead.
10
Tutorials
10.1.1 Introduction
• Basic application development using TERASOLUNA Server Framework for Java (5.x)
• Way of development using application layering of TERASOLUNA Server Framework for Java (5.x).
Target readers
• SQL knowledge
Verification environment
In this tutorial, operations are verified on following environment. In case of implementation on other environment,
perform appropriate setting based on this guideline.
Type Name
OS Windows 7
JVM Java 1.8
IDE Spring Tool Suite 3.6.4.RELEASE (Onwards referred as [STS])
Build Tool Apache Maven 3.3.9 (Onwards referred as [Maven])
Application Server Pivotal tc Server Developer Edition v3.1 (Enclosed in STS)
Web Browser Google Chrome 46.0.2490.80 m
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Application to manage TODO list is to be developed. TODO list display, TODO registration, TODO completion
and TODO deletion can be performed.
RuleID Description
B01 Only up to 5 incomplete TODO records can be registered
B02 For TODOs which are already completed, “TODO Complete” processing cannot be done.
Note: This application is for learning purpose only. It is not suitable as a real todo management application.
2028 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Create TODO
• When Business requirements of application B01 is not fulfilled, business exception with error code E001
is thrown
• Display “Created successfully!” at the transited screen when the creation process is successful.
Finish TODO
• For the TODOs corresponding to todoId which is received from the form object, change the status to
completed.
• When the corresponding TODO does not exist, resource not found exception with error code E404 is thrown
• When Business requirements of application B02 is not fulfilled, business exception with error code E002
is thrown
• Display “Finished successfully!” at the transited screen when the finishing process is successful.
Delete TODO
• When the corresponding TODO does not exist, resources undetected exception with error code E404 is
thrown
• Display “Deleted successfully!” at the transited screen when the deletion process is successful.
2030 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Under this tutorial, First, try the in-memory implementation followed by select myBatis3 or Spring Data JPA.
Project creation
First, create a blank project for implementation of infrastructure layer using mvn archetype:generate.
This is a procedure to create a blank project using the Windows command prompt.
Note: If internet connection is accessed through proxy server, In order to perform the following tasks, necessary
STS proxy settings and Maven proxy setting needs to be done.
Tip: If mvn archetype:generate executes on Bash, it can be executed by replacing the ^ with \.
If you want to create a project for RepositoryImpl using java.util.Map(without accessing the database),
run the following command to create O/R Mapper independent blank project in command prompt. If you read
through this tutorial in consecutive order, first of all, create a project in this way.
If you want to create a project for RepositoryImpl to access the database using MyBatis3, run the following
command to create a blank project for the MyBatis3. This way to create a project is to be done in Creating
infrastructure layer with MyBatis3.
If you want to create a project for RepositoryImpl to access the database using Spring Data JPA, run the following
command to create a blank project for the JPA. This way to create a project is to be done in Creating infrastructure
layer with Spring Data JPA.
Project import
Select the archetype created project from STS menu [File] -> [Import] -> [Maven] -> [Existing Maven Projects]
-> [Next].
Click on [Finish] by selecting C:\work\todo in Root Directory and selecting pom.xml of todo in Projects.
2032 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When the import is completed, project is displayed in the Package Explorer as follows.
Note: If the build error occurs after the import, it can be removed by Right click on the project name in Package
Explorer and select [Maven] -> [Update Project...] -> [OK] .
Tip: For better visibility, Package Presentation must be changed to [Hierarchical] from default [Flat].
Click [View Menu] (The right edge of the down arrow) of the Package Explorer and select [Package Presentation]
-> [Hierarchical].
2034 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Warning: H2 Database has been defined as a dependency in O/R Mapper type blank project but, this set-
ting is done to create a simple application easily therefore it is not intended to use in the actual application
development.
The following definitions shall be removed while performing the actual application development.
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Project configuration
Note: It had been recommended to use a multi-project structure in [Project Structure] section of previous chapter
but, in this tutorial, a single project configuration is used because it focuses on ease of learning. However, when
in a real project, multi project configuration is strongly recommended.
src
└ main
├ java
| └ todo
| ├ app
| | └ todo
| └ domain
| ├ model
| ├ repository
| | └ todo
| └ service
| └ todo
├ resources
| ├ META-INF
| | ├ mybatis ... (8)
| | └ spring
| └ todo
| └ domain
| └ repository ... (9)
| └ todo
└ wepapp
└ WEB-INF
└ views
(8)
[Configuration of blank project created for JPA, O/R Mapper independent blank project]
src
└ main
├ java
| └ todo
| ├ app ... (1)
| | └ todo
| └ domain ... (2)
| ├ model ... (3)
| ├ repository ... (4)
| | └ todo
2036 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(2)
(3)
(6)
(7)
Many settings those are required in advanced tutorial already done in the created blank project.
If only the implementation of the tutorial is concern, understanding of these settings are not required but it is
recommended that you understand what settings are necessary to run an application.
For description of the required configuration (settings file) to run an application, Refer [Description of the config-
uration file].
Note: If you are creating a Todo application for familiarizing with the system, you may skip the confirmation of
configuration file but suggest to read this after creating the Todo application.
Before starting the development of Todo application, verify the project operation.
Since the implementation of the Controller and JSP for displaying the top page are provided in the blank project,
it is possible to check the operation by displaying the top page.
package todo.app.welcome;
import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
// (1)
@Controller
public class HomeController {
// (2)
private static final Logger logger = LoggerFactory
.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
2038 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
// (3)
@RequestMapping(value = "/", method = {RequestMethod.GET, RequestMethod.POST})
public String home(Locale locale, Model model) {
// (4)
logger.info("Welcome home! The client locale is {}.", locale);
// (5)
model.addAttribute("serverTime", formattedDate);
// (6)
return "welcome/home";
}
(1) In order to make the Controller as component-scan target, attach @Controller annotation to class
level.
(3) Set mapping methods for accessing the "/" (root) using @RequestMapping annotation.
(4) Outputting info level log for notifying that the method is called.
(5) For displaying date on screen set date as "serverTime" attribute name to the Model.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Hello world!</h1>
<!-- (7) -->
<p>The time on the server is ${serverTime}.</p>
</div>
</body>
</html>
Right click on project and select [Run As] -> [Run on Server].
2040 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Select [Next] after selecting AP server (Pivotal tc Server Developer Edition v3.1).
Verify that todo is included in [Configured] and click [Finish] to start the server.
When started, log shown as below will be output. For "/" path, it is understood that hello method of
todo.app.welcome.HomeController is mapped.
Note: The TraceLoggingInterceptor outputs start and end log of the Controller. While ending, the
information of View and Model as well as processing time are output.
2042 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• Repository creation
• RepositoryImpl creation
• Service creation
• Application layer
• Controller creation
• Form creation
• View creation
About the creation of RepositoryImpl, implementation is differ depending on the type of the selected infrastructure
layer.
Here, In-memory implemented RepositoryImpl is created using java.util.Map without using the database is
explained. If you want to use database, create Todo application by referring the [Creating infrastructure layer with
a Database access] content.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
2044 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• ID = todoId
• Title = todoTitle
• Created on = createdAt
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
Tip: Getter/Setter methods can be generated automatically by using STS feature. After defining fields, right click
on editor and select [Source] -> [Generate Getter and Setters…]
2046 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Repository creation
Create TodoRepository interface. If you want use database, create Repository by referring the [Creating
infrastructure layer with a Database access] content.
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
Define following CRUD operation methods pertaining to this application in created interface.
package todo.domain.repository.todo;
import java.util.Collection;
import todo.domain.model.Todo;
Collection<Todo> findAll();
Note: Here, to improve versatility of TodoRepository, instead of (long countFinished()) method for
[Fetch completed record count], (long countByFinished(boolean)) method for [record count having xx
completion status ] is defined.
If pass true as an argument to long countByFinished(boolean), [completed record count] and if pass
false as an argument, [not completed record count] can be fetched by specification.
Here, In-memory implemented RepositoryImpl is created using java.util.Map for simplification. If you
want use database, create RepositoryImpl by referring the [Creating infrastructure layer with a Database access]
content.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
2048 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Business logic must not be included in RepositoryImpl, it should focus only on inserting and removing
(CRUD operation) from the persistence store.
package todo.domain.repository.todo;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Repository;
import todo.domain.model.Todo;
@Repository // (1)
public class TodoRepositoryImpl implements TodoRepository {
private static final Map<String, Todo> TODO_MAP = new ConcurrentHashMap<String, Todo>();
@Override
public Todo findOne(String todoId) {
return TODO_MAP.get(todoId);
}
@Override
public Collection<Todo> findAll() {
return TODO_MAP.values();
}
@Override
public void create(Todo todo) {
TODO_MAP.put(todo.getTodoId(), todo);
}
@Override
public boolean update(Todo todo) {
TODO_MAP.put(todo.getTodoId(), todo);
return true;
}
@Override
public void delete(Todo todo) {
TODO_MAP.remove(todo.getTodoId());
}
@Override
public long countByFinished(boolean finished) {
long count = 0;
for (Todo todo : TODO_MAP.values()) {
if (finished == todo.isFinished()) {
count++;
}
}
return count;
}
}
(1) To consider Repository as component scan target, add @Repository annotation at class
level.
Note: In this tutorial, although infrastructure layer belonging classes (RepositoryImpl) are stored under domain
layer package (todo.domain), if package is divided completed on the basis of layers, it is better to create classes
of infrastructure layer under todo.infra.
However, in a normal project, infrastructure layer rarely changes (such projects are less). Hence, in order to
improve the work efficiency, RepositoryImpl can be created in the layer same as the repository of domain layer.
Service creation
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
2050 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
package todo.domain.service.todo;
import java.util.Collection;
import todo.domain.model.Todo;
Next, create TodoServiceImpl class that implements the methods defined in TodoService interface.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
2052 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service// (1)
@Transactional // (2)
public class TodoServiceImpl implements TodoService {
@Inject// (3)
TodoRepository todoRepository;
// (4)
public Todo findOne(String todoId) {
Todo todo = todoRepository.findOne(todoId);
if (todo == null) {
// (5)
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E404] The requested Todo is not found. (id="
+ todoId + ")"));
// (6)
throw new ResourceNotFoundException(messages);
}
return todo;
}
@Override
@Transactional(readOnly = true) // (7)
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E001] The count of un-finished Todo must not be over "
+ MAX_UNFINISHED_COUNT + "."));
// (8)
throw new BusinessException(messages);
}
// (9)
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.create(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo); // 10
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E002] The requested Todo is already finished. (id="
+ todoId + ")"));
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.update(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo); // (11)
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
2054 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) To consider Service as component-scan target, add @Service annotation at class level.
(2) All public methods will be treated as transaction management by attaching the @Transactional
annotation at class level.
By applying annotation, the transaction starts at the timing of method execution starts and transaction
commits at the time of method execution successful completion.
However, if unexpected exception occurs in between, the transaction roll-backs.
(4) Logic of fetching single record is used in both delete and finish methods. Hence it should be
implemented in a method (OK to make it public by declaring in the interface).
Note: In this chapter, error messages are hard coded for simplification, but in reality it is not preferred from
maintenance viewpoint. Usually, it is recommended to create message externally in property file. The method for
creating the external property file is described in Properties Management.
Todo
TBD
For information about how to Unit test the service, planned to be described in next version later.
Since domain layer implementation is completed, use the domain layer to create application layer.
Creation of Controller
First create Controller that controls screen transition of todo business application.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
Note: It should be noted that the higher level package is different from the domain layer.
2056 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
package todo.app.todo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // (1)
@RequestMapping("todo") // (2)
public class TodoController {
(1) In order to make Controller as component-scan target, add @Controller annotation at class level.
Right click on the Package Explorer, select -> [New] -> [Class] -> [New Java Class] dialog box appears,
• Title = todoTitle
package todo.app.todo;
import java.io.Serializable;
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
2058 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject // (1)
TodoService todoService;
@ModelAttribute // (2)
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
By adding @ModelAttribute annotation, form object of the return value of this method is added
to Model with name "todoForm".
It is same as executing model.addAttribute("todoForm", form) in each method of
TodoController.
(3) set @RequestMapping annotation such a way that method of list screen display (list method)
gets executed when it is requested to /todo/list path.
Right click on the Package Explorer, select -> [New] -> [File] -> [New File] dialog box appears,
2060 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<!-- (2) -->
<form:input path="todoTitle" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<!-- (3) -->
<c:forEach items="${todos}" var="todo">
<li><c:choose>
2062 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(5) To take XSS countermeasures at the time of output of character string, HTML escape should be
performed using f:h() function.
Regarding XSS measures, refer to XSS Countermeasures.
Right click [todo] project in STS and start Web application by [Run As] -> [Run on Server]. If
http://localhost:8080/todo/todo/list is accessed in browser, the following screen gets displayed.
Create TODO
Next, implement a new creation logic after clicking [Create TODO] button on List display screen.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.Valid;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
// (1)
@Inject
Mapper beanMapper;
2064 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
// (5)
if (bindingResult.hasErrors()) {
return list(model);
}
// (6)
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
// (7)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (8)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
(1) At the time of converting form object into domain object. Inject the Mapper interface of Dozer.
(2) Set @RequestMapping annotation such a way that method of new creation process (create
method) gets executed when it is requested to /todo/create path using POST method.
(3) For performing input validation of form, add @Valid annotation to form argument. Input validation
result is stored in the immediate next argument BindingResult.
(6) Create Todo object from TodoForm object using Mapper interface of Dozer.
No need to set if the property name of conversion source and destination is the same.
There is no merit in using Mapper interface of Dozer to convert only todoTitle property, but it is
very convenient in case of multiple properties.
(7) In case of BusinessException while executing business logic, add the result message to Model
and return to list screen.
(8) Since it is created successfully, add the result message to flash scope and redirect to list screen.
Since redirect is used, there is no case of browser being read again and a new registration process
being POST. (For details, refer to “About PRG (Post-Redirect-Get) pattern”)
Since this time Created successfully message is displayed, ResultMessages.success() is
used.
2066 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Modifications in Form To define input validation rules, add annotation to the form object.
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull // (1)
@Size(min = 1, max = 30) // (2)
private String todoTitle;
Modifications in JSP Add the tag for displaying the result message and input check error.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<style type="text/css">
.strike {
text-decoration: line-through;
}
</style>
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<!-- (1) -->
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" /><!-- (2) -->
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span style="text-decoration: line-through;">
${f:h(todo.todoTitle)}
</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
(2) Display errors in case of input error using <form:errors> tag. Match the value of
path attribute with <form:input> tag.
If form is submitted by entering appropriate value in the form, success message is displayed as given below.
When 6 or more records are registered and business error occurs, error message is displayed.
If form is submitted by entering null character, the following error message is displayed.
2068 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
With the following modifications in style sheet (in <style> tag of list.jsp), customize appearance of the
result message.
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
Moreover, input error message class can be specified to cssClass attribute of <form:errors> tag.
2070 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
.text-error {
color: #c60f13;
}
Finish TODO
Add [Finish] button to List display screen and add completion process of TODO.
The todoId property needs to be added to TodoForm but if simply added, todoId property check rules for
new creation are applied as it is. For specifying separate rules for new creation and completion in a single Form,
set groups attribute and perform input check rule group.
• ID → todoId
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
// (2)
@NotNull(groups = { TodoFinish.class })
private String todoId;
// (3)
@NotNull(groups = { TodoCreate.class })
@Size(min = 1, max = 30, groups = { TodoCreate.class })
private String todoTitle;
2072 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Take precaution of using @Validated instead of @Valid for executing the group validation.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
2074 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
// (5)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (6)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
}
Group of input check rules (group interface) can be specified in value attribute.
Default.class is a group interface provided to apply an input validation rules when group is not
specified.
(2) Set @RequestMapping annotation such a way that method of completion process (finish
method) gets executed when it is requested to /todo/finish path using POST method.
(3) Specify the group interface (TodoFinish interface) for Finish processing as group of input check.
(5) In case of BusinessException while executing business logic, add the result message to Model
and return to list screen.
(6) Since it is created successfully, add the result message to flash scope and redirect to list screen.
Note: Separate Form can also be created for Create and Finish. In case of separate Form class, there is no need
to group the input check rules therefore definition of input check rules will be simple.
• Not possible to centrally manage the input check rules due to duplicate properties increases
Therefore, please note that when the specifications changes, the modification cost will also be more.
Moreover, if multiple Form objects are initialized by @ModelAttribute method, unnecessary instance gets
generated because every time all Forms are being initialized.
2076 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (2) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose></li>
</c:forEach>
</ul>
</div>
</body>
</html>
(1) Display the form for sending the request to complete the TODO if there are incomplete Todo.
Specify URL(<contextPath>/todo/finish) into action attribute for running the
completion process.
Since the completion process is a process of updating, specify the POST method into the method
attribute.
When pressing the [Finish] button after newly creating Todo, strike-through is shown as below and it can be
understood that the operation is completed.
2078 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Delete TODO
Add [Delete] button on the list display screen and add the deletion process for TODO removal.
package todo.app.todo;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
// (1)
public static interface TodoDelete {
}
// (2)
@NotNull(groups = { TodoFinish.class, TodoDelete.class })
private String todoId;
@NotNull(groups = { TodoCreate.class })
(1) Create the TodoDelete interface for deletion processing as group of input check rule.
Modifications in Controller Add the logic for delete processing to TodoController. It is almost same as
the completion process.
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
import javax.validation.groups.Default;
import org.dozer.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
2080 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.app.todo.TodoForm.TodoDelete;
import todo.app.todo.TodoForm.TodoCreate;
import todo.app.todo.TodoForm.TodoFinish;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@Controller
@RequestMapping("todo")
public class TodoController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection<Todo> todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.create(todo);
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
try {
todoService.finish(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Finished successfully!")));
return "redirect:/todo/list";
}
if (bindingResult.hasErrors()) {
return list(model);
}
try {
todoService.delete(form.getTodoId());
} catch (BusinessException e) {
model.addAttribute(e.getResultMessages());
return list(model);
}
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Deleted successfully!")));
return "redirect:/todo/list";
}
2082 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
</head>
<style type="text/css">
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
</style>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose>
<!-- (1) -->
<form:form
action="${pageContext.request.contextPath}/todo/delete"
method="post" modelAttribute="todoForm"
cssStyle="display: inline-block;">
<!-- (2) -->
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Delete</form:button>
</form:form>
</li>
</c:forEach>
</ul>
</div>
</body>
</html>
2084 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When pressing the [Delete] button in an uncompleted TODO state, TODO is deleted as follows.
Although style sheets are directly defined in a JSP file, generally it is defined in the CSS file while developing the
actual application.
Here, how to define style sheet into the CSS file are described.
/* ... */
.strike {
text-decoration: line-through;
}
.alert {
border: 1px solid;
margin-bottom: 5px;
}
.alert-error {
background-color: #c60f13;
border-color: #970b0e;
color: white;
}
.alert-success {
background-color: #5da423;
border-color: #457a1a;
color: white;
}
.text-error {
color: #c60f13;
}
.alert ul {
margin: 15px 0px 15px 0px;
}
#todoList li {
margin-top: 5px;
}
2086 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Todo List</title>
<!-- (1) -->
<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/app/css/styles.css" type
</head>
<body>
<h1>Todo List</h1>
<div id="todoForm">
<t:messagesPanel />
<form:form
action="${pageContext.request.contextPath}/todo/create"
method="post" modelAttribute="todoForm">
<form:input path="todoTitle" />
<form:errors path="todoTitle" cssClass="text-error" />
<form:button>Create Todo</form:button>
</form:form>
</div>
<hr />
<div id="todoList">
<ul>
<c:forEach items="${todos}" var="todo">
<li><c:choose>
<c:when test="${todo.finished}">
<span class="strike">${f:h(todo.todoTitle)}</span>
</c:when>
<c:otherwise>
${f:h(todo.todoTitle)}
<form:form
action="${pageContext.request.contextPath}/todo/finish"
method="post"
modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Finish</form:button>
</form:form>
</c:otherwise>
</c:choose>
<form:form
action="${pageContext.request.contextPath}/todo/delete"
method="post" modelAttribute="todoForm"
cssStyle="display: inline-block;">
<form:hidden path="todoId"
value="${f:h(todo.todoId)}" />
<form:button>Delete</form:button>
</form:form>
</li>
</c:forEach>
</ul>
</div>
</body>
</html>
(1) Delete the style sheet definitions from the JSP file and load the CSS file in which the style sheets are
defined.
In this section, infrastructure layer for persisting Domain objects in the database is explained.
In this tutorial, it explains how to implement the infrastructure layer using following two O/R Mapper.
• MyBatis3
2088 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Next, copy a file other than **TodoRepositoryImpl class to a newly created projectunder srcfolder created
upto Creating infrastructure layer with a Database access.
However, file to be copied must be a newly created file or file with added changes. A file without modifica-
tions must not be copied.
Database set-up
In this tutorial, the H2 Database is used to save the database setup time.
Modification in todo-infra.properties
database=H2
# (1)
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1;INIT=create table if not exists todo(todo_id varch
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
(1) Specify the DDL statements to create tables into INIT parameter of the URL connection.
Note: If you format the DDL statement that is set to INIT parameter, it will looks like follows.
If you want to use the Spring Data JPA, you can skip this section and may proceed to the Creating infrastructure
layer with Spring Data JPA.
Create TodoRepository
TodoRepository is created by the same way as it is created for without O/R Mapper. For creation method,
refer [Repository creation].
Create TodoRepositoryImpl
If MyBatis3 is used, RepositoryImpl is automatically generated from the Repository interface (Mapper interface).
Therefore, the creation of TodoRepositoryImpl is not required. Remove if it is created.
Create a Mapper file for defining SQL to be executed when the TodoRepository interface methods are called.
Right click on the Package Explorer, select -> [New] -> [File] -> [New File] dialog box appears,
2090 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Describe the SQL to be executed when the TodoRepository methods defined in the interfaces are called.
</select>
2092 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
DELETE FROM
todo
WHERE
todo_id = #{todoId}
]]>
</delete>
</mapper>
(1) Specify the fully qualified class name of the Repository interfaces (FQCN) in the namespace
attribute of mapper element.
(2) Define JavaBean mapping with search result (ResultSet) in the <resultMap> element.
For the mapping file details, Refer Database Access (MyBatis3).
(3) Implement the SQL to get one record which matches with the todoId (PK).
Specify the ID of applicable mapping definition in the resultMap attribute of <select> element.
(5) Implement the SQL to insert the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the parameterType attribute of
<insert> element.
(6) Implement the SQL to update the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the parameterType attribute of
<update> element.
(7) Implement the SQL to delete the Todo object specified in the argument.
Specify the parameter of the class name (FQCN or alias name) in the parameterType attribute of
<delete> element.
(8) Implement the SQL to get all records matches with the finished status specified in the argument.
2094 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Since the creation of infrastructure layer using MyBatis3 has been completed, create the application layer compo-
nents and services.
Once creation of the Services and application layer are completed, execute the Todo application after starting the
AP server, SQL and transaction log output is as following.
Here, How to create RepositoryImpl of infrastructure layer using Spring Data JPA is explained.
Modification in Entity
Configure the JPA annotation for mapping the Todo class with TODO table of Database.
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
// (1)
@Entity
@Table(name = "todo")
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
// (2)
@Id
private String todoId;
// (3)
@Temporal(TemporalType.TIMESTAMP)
private Date createdAt;
2096 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
return createdAt;
}
(1) Add @Entity showing that it is JPA entity and set the corresponding table name using @Table.
Creat TodoRepository
Right click on the Package Explorer, select -> [New] -> [Interface] -> [New Java Interface] dialog box appears,
package todo.domain.repository.todo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import todo.domain.model.Todo;
// (1)
public interface TodoRepository extends JpaRepository<Todo, String> {
(2) Specify @Query annotation to the JPQL executed at the time of calling countByFinished
method
Create TodoRepositoryImpl
If Spring Data JPA is used, RepositoryImpl is automatically generated from the Repository interface. Therefore,
the creation of TodoRepositoryImpl is not required. Remove if it is created.
Since the creation of infrastructure layer using Spring Data JPA has been completed, create the application layer
components and services.
Once creation of the Services and application layer are completed, execute the Todo application after starting the
AP server, SQL and transaction log output is as following.
2098 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• How to develop basic applications by TERASOLUNA Server Framework for Java (5.x)
• Way of development using application layering of TERASOLUNA Server Framework for Java (5.x).
• Implementation of application layer with the use of JSP tag libraries and POJO(+ Spring MVC)
The following improvement can be done in the TODO management application. As a learning challenge of
the application improvement refer the appropriate description of the guidelines.
• To externalize the property (Maximum number of uncompleted TODO) -> Properties Management
• To add double submit protection (Support the transaction token check) -> Double Submit Protection
• To change how to get the system date time -> System Date
10.1.7 Appendix
Description of the configuration files are done for giving an understanding of what type of settings are required to
run an application. Settings that are not used in created Todo tutorial application are ignored here.
web.xml
In web.xml, the settings are done for deploying the Todo application as a Web application.
<filter>
<filter-name>exceptionLoggingFilter</filter-name>
2100 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>XTrackMDCPutFilter</filter-name>
<filter-class>org.terasoluna.gfw.web.logging.mdc.XTrackMDCPutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XTrackMDCPutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<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>
<param-value>classpath*:META-INF/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2102 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
10.1.
(6) TutorialDefinition
(Todo Application)
of error page. 2103
Common JSP
Tag library settings applied to all JSP are done in Include JSP.
(4) Definition tag library for Spring Security.(However, it is not used in this tutorial)
2104 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Following Bean definition files and property files are generated in created blank project.
• src/main/resources/META-INF/spring/applicationContext.xml
• src/main/resources/META-INF/spring/todo-domain.xml
• src/main/resources/META-INF/spring/todo-infra.xml
• src/main/resources/META-INF/spring/todo-infra.properties
• src/main/resources/META-INF/spring/todo-env.xml
• src/main/resources/META-INF/spring/spring-mvc.xml
• src/main/resources/META-INF/spring/spring-security.xml
Note: The todo-infra.properties and todo-env.xml are not created while creating blank project
that does not dependent on the O/R Mapper.
Note: In this guideline, it is recommended to split Bean definition file for each role (layer).
By this way, what is defined in which file can be easily understood and improves the ease of maintenance. It is not
effective in case of small application like tutorial but more effective in case of large scale application.
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org
http://www.springframework.org/schema/context http://www.springframework.org/schema/contex
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring
2106 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<bean id="exceptionLoggingFilter"
class="org.terasoluna.gfw.web.exception.ExceptionLoggingFilter" >
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
</beans>
Tip: By inserting following type of check-mark in [Configure Namespace] tab of the editor, selected XML
schema gets enabled and possible to supplement the input using Ctrl+Space at the time of XML editing.
It is recommended to select xsd file without version in [Namespace Versions]. By selecting the xsd file without
version, always the latest xsd is used included in the jar therefore no need to concern about the Spring version up.
todo-domain.xml Perform the domain layer related settings of the Todo application in todo-domain.xml.
2108 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
pointcut="@within(org.springframework.stereotype.Service)" />
</aop:config>
</beans>
Note: The <tx:annotation-driven> tag is set in order to enable the transaction management by
@Transactional annotation while creating O/R Mapper dependent blank project.
<tx:annotation-driven />
todo-infra.xml Perform infrastructure layer related settings of the TODO application in todo-infra.xml.
todo-infra.xml if creating blank project that does not dependent on the O/R Mapper Empty definition file
is created as follows while creating blank project that does not dependent on the O/R Mapper.
</beans>
todo-infra.xml of blank project created for MyBatis3 The following settings are done in created MyBatis3
project.
</beans>
2110 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Import the Bean definition file that defines the environment dependent components(Such as
DataSource and transaction manager).
The connection is retrieved from the DataSource specified here by executing the SQL in MyBatis3
process.
(4) Specify the path of MyBatis configuration file in the configLocation property.
Scanned the Mapper interface that stored under the specified package and
automatically generated the thread-safe Mapper object (Proxy object of Mapper interface).
<typeAliases>
<package name="todo.domain.model" />
<package name="todo.domain.repository" />
<!--
<package name="todo.infra.mybatis.typehandler" />
-->
</typeAliases>
<typeHandlers>
<!--
<package name="todo.infra.mybatis.typehandler" />
-->
</typeHandlers>
</configuration>
todo-infra.xml of blank project created for JPA The following settings are done in created JPA blank project.
2112 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<bean
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
id="entityManagerFactory">
<!-- (5) -->
<property name="packagesToScan" value="todo.domain.model" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<!-- (6) -->
<property name="jpaPropertyMap">
<util:map>
<entry key="hibernate.hbm2ddl.auto" value="" />
<entry key="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.ImprovedNamingStrategy" />
<entry key="hibernate.connection.charSet" value="UTF-8" />
<entry key="hibernate.show_sql" value="false" />
<entry key="hibernate.format_sql" value="false" />
<entry key="hibernate.use_sql_comments" value="true" />
<entry key="hibernate.jdbc.batch_size" value="30" />
<entry key="hibernate.jdbc.fetch_size" value="100" />
</util:map>
</property>
</bean>
</beans>
(1) Import the Bean definition file that defines the environment dependent components(Such as
DataSource and transaction manager).
(2) Automatically generate the implementation class from Repository interface using the Spring Data
JPA.
Specify the package in base-package attribute of <jpa:repository> tag that contains the
Repository.
(5) Specify the package name that treated as a JPA entity classes.
The todo-infra.properties is not created while creating blank project that does not dependent on the O/R
Mapper.
# (1)
database=H2
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1;
database.username=sa
2114 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
database.password=
database.driverClassName=org.h2.Driver
# (2)
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
todo-env.xml In the todo-env.xml file, define such components which are differing depending upon the
environment.
Here, the file stored in the blank project for MyBatis3 is described as an example. Furthermore, the
todo-env.xml is not created while creating blank project that does not access the database.
2116 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: If blank project created for JPA, Transaction Manager, transaction controlling class
(org.springframework.orm.jpa.JpaTransactionManager) is configured using JPA API.
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<mvc:default-servlet-handler />
<mvc:interceptors>
<!-- (5) -->
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
2118 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
<!-- REMOVE THIS LINE IF YOU USE JPA
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
REMOVE THIS LINE IF YOU USE JPA -->
</mvc:interceptors>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<bean class="org.springframework.security.web.servlet.support.csrf.CsrfRequestData
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataVal
</util:list>
</constructor-arg>
</bean>
<map>
<entry key="common/error/resourceNotFoundError" value="404" />
<entry key="common/error/businessError" value="409" />
<entry key="common/error/transactionTokenError" value="409" />
<entry key="common/error/dataAccessError" value="500" />
</map>
</property>
<property name="defaultErrorView" value="common/error/systemError" />
<property name="defaultStatusCode" value="500" />
</bean>
<!-- Setting AOP. -->
<bean id="handlerExceptionResolverLoggingInterceptor"
class="org.terasoluna.gfw.web.exception.HandlerExceptionResolverLoggingInterceptor">
<property name="exceptionLogger" ref="exceptionLogger" />
</bean>
<aop:config>
<aop:advisor advice-ref="handlerExceptionResolverLoggingInterceptor"
pointcut="execution(* org.springframework.web.servlet.HandlerExceptionResolver.resolve
</aop:config>
</beans>
2120 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(3) Components under todo.app package that holds classes of application layer are made target of
component-scan.
(4) Carry out the settings for accessing the static resource (css, images, js etc.).
Set URL path to mapping attribute and physical path to location attribute.
In case of this setting, when there is a request for
<contextPath>/rerources/app/css/styles.css, the
WEB-INF/resources/app/css/styles.css is searched. If not found,
META-INF/resources/app/css/styles.css is searched in classpath
(src/main/resources and jar).
If styles.css is not stored anywhere, 404 error is returned.
Here, cache period (3600 seconds = 60 minutes) of static resources is set in cache-period
attribute.
cache-period="3600" is also correct, however, in order to demonstrate that it is 60 minutes, it
is better to write as cache-period="#{60 * 60}" which uses SpEL.
Tip: <mvc:view-resolvers> element is a XML element that added from Spring Framework
4.1 By using <mvc:view-resolvers> element, it is possible to define ViewResolver simply.
The definition example of using the conventional <bean> element is shown below.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
OpenEntityManagerInViewInterceptor is a Interceptor that performs start and end of the life cycle
of the EntityManager. By adding this setting, the application layer (Controller or View class) Lazy Load is
supported.
2122 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<sec:authentication-manager></sec:authentication-manager>
</beans>
logback.xml
2124 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<logger name="org.springframework.web.servlet">
<level value="info" />
</logger>
<logger name="jdbc.sqltiming">
<level value="debug" />
</logger>
<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="APPLICATION_LOG_FILE" />
</root>
</configuration>
(2) Set so that log of debug level and above is output under todo package.
(3) Set log level to ‘trace’ for TraceLoggingInterceptor which is defined in spring-mvc.xml.
Note: If you create a blank project that uses the O/R Mapper, the logger to output transaction control-related log
has been in enable state.
<logger name="org.hibernate.engine.transaction">
<level value="debug" />
</logger>
<logger name="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<level value="debug" />
</logger>
2126 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
10.2.1 Introduction
• Basic RESTful web service development using TERASOLUNA Server Framework for Java (5.x)
Target readers
Verification environment
Type Product
REST Client DHC REST Client 1.2.3
Product other than the above Similar to Tutorial (Todo Application)
Assumes that Java, STS, Maven, Google Chrome are already installed since the Tutorial (Todo Application) is
implemented.
Install DHC
2128 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
When you open the application list (Open by specifying [chrome://apps/] in your browser address bar) of Chrome,
DHC has been added.
Project creation
In this tutorial, the RESTful Web Services are created for [Tutorial (Todo Application)].
Therefore, if [Tutorial (Todo Application)] project is not exists, re-create project by executing [Tutorial (Todo
Application)].
Note: If project is re-created by executing [Tutorial (Todo Application)], it is possible to proceed further this
tutorial by performing re-creation till the domain layer creation.
In this tutorial, creating REST API for publishing the data on Web which are managed in the todo table (here
onwards called as [Todo Resources]).
2130 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Tip: The {todoId} included in path is called as path variable and can deal with any changeable value. The
GET /api/v1/todos/123 and GET /api/v1/todos/456 can be handle with same API using the path
variable.
In this tutorial, We are dealing with ID (Todo ID) as the path variable in order to uniquely identifying the Todo.
API specification
Indicated the REST API Interface specifications using specific example of the HTTP requests and responses in
this tutorial.
HTTP headers which are not essential have been excluded from the example.
GET Todos
[Request]
[Response]
POST Todos
[Request]
[Response]
2132 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
GET Todo
[Request]
Specify ID of the Todo Resource in [todoId] path variable that you want to fetch.
In below example, 9aef3ee3-30d4-4a7c-be4a-bc184ca1d558 is specified in [todoId] path variable.
[Response]
Return Todo Resource in JSON format that matches with the [todoId] path variable.
PUT Todo
[Request]
Specify ID of the Todo Resource in [todoId] path variable that you want to update.
In the PUT Todo, interface specification does not receive the request BODY because Todo Resource is only
updating into completion state.
[Response]
Return Todo Resource in JSON format that matches with the [todoId] path variable after updating in completed
status (true of finished field).
DELETE Todo
[Request]
Specify ID of the Todo Resource in [todoId] path variable that you want to delete.
[Response]
In the DELETE Todo, since the Todo Resource is deleted and resource that can return is no longer exists, interface
specification does not return the response BODY.
Error Response
Return error in JSON format in case of any error occurs in REST API.
The response specification of typical errors are described below.
Error patterns other than the below are also exists but description in the tutorial are omitted.
In the Tutorial (Todo Application), error messages are hardcoded in the program but in this tutorial, it is modified
such a way that the error messages are retrieved from the property file based on error code.
2134 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
First, add the definition of DispatcherServlet for processing the REST API request.
Modification of web.xml
classpath*:META-INF/spring/spring-security.xml
</param-value>
</context-param>
<filter>
<filter-name>MDCClearFilter</filter-name>
<filter-class>org.terasoluna.gfw.web.logging.mdc.MDCClearFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MDCClearFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>exceptionLoggingFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>exceptionLoggingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>XTrackMDCPutFilter</filter-name>
<filter-class>org.terasoluna.gfw.web.logging.mdc.XTrackMDCPutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>XTrackMDCPutFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
2136 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- ApplicationContext for Spring MVC -->
<param-value>classpath*:META-INF/spring/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>false</el-ignored>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
<include-prelude>/WEB-INF/views/common/include.jsp</include-prelude>
</jsp-property-group>
</jsp-config>
<error-page>
<error-code>500</error-code>
<location>/WEB-INF/views/common/error/systemError.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/common/error/resourceNotFoundError.jsp</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/WEB-INF/views/common/error/unhandledSystemError.html</location>
</error-page>
<session-config>
<!-- 30min -->
<session-timeout>30</session-timeout>
</session-config>
</web-app>
Sr. No Description
(1) Specify the SpringMVC configuration file for REST at the initialization parameter
[contextConfigLocation].
In this tutorial, [META-INF/spring/spring-mvc-rest.xml] is specified located at class
path.
(2) Specify the URL pattern that maps to the DispatcherServlet for REST API at
<url-pattern> element.
In this tutorial, if it starts from /api/v1/, request is considered as a REST API request and mapped
with the DispatcherServlet for REST API.
Creation of spring-mvc-rest.xml
2138 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/resources/META-INF/spring/spring-mvc-rest.xml
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalA
</mvc:argument-resolvers>
<mvc:message-converters register-defaults="false">
<!-- (1) -->
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
<!-- (2) -->
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<!-- (3) -->
<bean class="com.fasterxml.jackson.databind.util.StdDateFormat"/>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<!-- REMOVE THIS LINE IF YOU USE JPA
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
REMOVE THIS LINE IF YOU USE JPA -->
</mvc:interceptors>
</beans>
2140 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Sr. No Description
Set the class(org.springframework.http.converter.HttpMessageConverter) to
serialize/de-serialize the JavaBean dealing with arguments and return values of the Controller at
(1)
<mvc:message-converters>.
Multiple HttpMessageConverter can be configured but, since only JSON is used in this tutorial
only MappingJackson2HttpMessageConverter is specified.
Specify the ObjectMapper(Component for conversion of [JSON <-> Jav-
aBean]) that is provided by Jackson into the objectMapper property of the
(2)
MappingJackson2HttpMessageConverter.
In this tutorial, Data format customized ObjectMapper is specified. objectMapperproperty can
be omitted if customization is not required.
Specify format of the Date field into dateFormat property of ObjectMapper.
In this tutorial, ISO-8601 format used while serializing java.util.Date object. If you want
(3)
to use ISO-8601 format while serializing Date object, it can be implemented by configuring the
com.fasterxml.jackson.databind.util.StdDateFormat.
Scan the components under the package of the REST API
In this tutorial, the package of REST API is todo.api. Although the Controllers for the screen
(4)
transition had been stored under app package, Controllers for REST API are recommended to store
under api package.
If you disable the CSRF protection, the use of session is not required.
Therefore, in this tutorial, adopted an architecture that does not use the session(stateless architecture) and disable
the CSRF measures.
The use of session and CSRF measures can be avoided by adding the following settings.
src/main/resources/META-INF/spring/spring-security.xml
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.or
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/sp
http://www.springframework.org/schema/context http://www.springframework.org/schema/contex
<sec:http>
<sec:form-login />
<sec:logout />
<sec:access-denied-handler ref="accessDeniedHandler"/>
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER"/>
<sec:session-management />
</sec:http>
<sec:authentication-manager></sec:authentication-manager>
2142 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp" />
</bean>
</constructor-arg>
</bean>
</beans>
Sr. No Description
The name of the root package is api that contains the REST API classes and recommended to create a package
of each resource (lowercase resource name) under it.
Since the name of the resource is Todo in this tutorial, the todo.api.todo package is created.
Note: Usually following three types of classes are stored in the created package. The following naming rules are
recommended for the classes.
• [Resource name]Resource
• [Resource name]RestController
• TodoResource
• TodoRestController
is created
src/main/java/todo/api/todo/TodoResource.java
package todo.api.todo;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@NotNull
2144 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: The reason for creating a Resource class in spite of the existence of the DomainObject class (Todo class
in this tutorial), business process is not consistent with the interface to be used in the input and output of the client.
If it is used wrongly, application layer will be impacted to the domain layer and also decrease the maintainability.
It is recommended to perform the data conversion using BeanMapper such as Dozer by creating DomainObject
and Resource class separately.
The role of the Resource class is similar to the Form class but eventually it is differing like Form class represent
the <form> tag of HTML in JavaBean and Resource class is the input and output of the REST API in JavaBean.
However, it is a JavaBean having annotation of the Bean Validation and the Controller class is approximately the
same as the Form class because stored in the same package.
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // (1)
@RequestMapping("todos") // (2)
public class TodoRestController {
Sr. No Description
Implement the processing of API(GET Todos) into getTodos method of TodoRestController that fetches
all records of created Todo Resource.
2146 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
Sr. No Description
(1) Set the RequestMethod.GET to method attribute for handling the GET request.
(2) Specify @ResponseStatus annotation to the HTTP status code for response.
To set “200 OK” as a HTTP status, set the HttpStatus.OK to the value attribute.
(3) Converting Todo object returned from findAll method of TodoService into TodoResource
object type that represent JSON response.
It is convenient to use the org.dozer.Mapper interface of Dozer for converting Todo and
TodoResource.
2148 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Since the Spring Security setting has been changed to not use the session therefore want to focus on the point that
"Set-Cookie: JSESSIONID=xxxx" is not exists in the [RESPONSE] [HEADERS].
Implement the processing of API(GET Todos) into postTodos method of TodoRestController that create
new Todo Resource.
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
2150 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Mapper beanMapper;
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<TodoResource> getTodos() {
Collection<Todo> todos = todoService.findAll();
List<TodoResource> todoResources = new ArrayList<>();
for (Todo todo : todos) {
todoResources.add(beanMapper.map(todo, TodoResource.class));
}
return todoResources;
}
Sr. No Description
(1) Set the RequestMethod.POST to method attribute for handling the POST request.
(2) Specify @ResponseStatus annotation to the HTTP status code for response.
To set “201 Created” as a HTTP status, set the HttpStatus.CREATED to the value attribute.
(3) In order to map the HTTP request Body(JSON) with JavaBean, grant @RequestBody annotation to
the mapping targeted TodoResource class.
Furthermore, grant @Validated annotation for input check. It is necessary to handle exception
separately.
(4) Create new Todo resource by executing create method of TodoService after converting
TodoResource into Todo class.
(5) Converting Todo object created by create method of TodoService into TodoResource type
that represent JSON response.
{
"todoTitle": "Hello World!"
2152 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Furthermore, Add HTTP header by [+] button of [REQUEST] [HEADERS] and click “Send” button after setting
[application/json] in the [Content-Type].
HTTP status returned “201 Created” and JSON of the newly created Todo resource displays in [Body] of [RE-
SPONSE] part.
If GET Todos gets executed now, newly created Todo Resource returns as an array.
Since method (findOne) for retrieving single item is not created in TodoService of the Tutorial (Todo Appli-
cation), add the following highlighted parts in TodoService and TodoServiceImpl.
package todo.domain.service.todo;
import java.util.Collection;
import todo.domain.model.Todo;
2154 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Set a read-only transaction that is initiated at the time of calling findOne method.
src/main/java/todo/domain/service/todo/TodoServiceImpl.java
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessage;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service
@Transactional
public class TodoServiceImpl implements TodoService {
@Inject
TodoRepository todoRepository;
@Override
@Transactional(readOnly = true)
public Todo findOne(String todoId) {
Todo todo = todoRepository.findOne(todoId);
if (todo == null) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E404] The requested Todo is not found. (id="
+ todoId + ")"));
throw new ResourceNotFoundException(messages);
}
return todo;
}
@Override
@Transactional(readOnly = true)
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
2156 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.create(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo);
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add(ResultMessage
.fromText("[E002] The requested Todo is already finished. (id="
+ todoId + ")"));
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.update(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo);
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
Implement the processing of retrieving single Todo Resource API(GET Todo) into getTodo method of
TodoRestController .
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<TodoResource> getTodos() {
Collection<Todo> todos = todoService.findAll();
List<TodoResource> todoResources = new ArrayList<>();
for (Todo todo : todos) {
todoResources.add(beanMapper.map(todo, TodoResource.class));
}
return todoResources;
}
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public TodoResource postTodos(@RequestBody @Validated TodoResource todoResource) {
Todo createdTodo = todoService.create(beanMapper.map(todoResource, Todo.class));
2158 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Sr. No Description
(1) In order to get the todoId from path, specify the path variable in the value attribute of the
@RequestMapping annotation.
Set the RequestMethod.GET to method attribute for handling the GET request.
(2) Specify the path variable name to retrieve todoId in the value attribute of the @PathVariable
annotation.
(3) You can use the todoId obtained from path variable to get one Todo resource.
HTTP status returned “200 OK” and JSON of the indicated Todo resource displays in [Body] of [RESPONSE]
part.
Implement the processing of API(PUT Todo) into putTodo method of TodoRestController that up-
dates(updating into completed status) one record of Todo Resource.
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
2160 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public List<TodoResource> getTodos() {
Collection<Todo> todos = todoService.findAll();
List<TodoResource> todoResources = new ArrayList<>();
for (Todo todo : todos) {
todoResources.add(beanMapper.map(todo, TodoResource.class));
}
return todoResources;
}
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public TodoResource postTodos(@RequestBody @Validated TodoResource todoResource) {
Todo createdTodo = todoService.create(beanMapper.map(todoResource, Todo.class));
TodoResource createdTodoResponse = beanMapper.map(createdTodo, TodoResource.class);
return createdTodoResponse;
}
return finishedTodoResource;
}
Sr. No Description
(1) In order to get the todoId from path, specify the path variable in the value attribute of the
@RequestMappingannotation.
Set the RequestMethod.PUT to method attribute for handling the PUT request.
(2) Specify the path variable name to retrieve todoId in the value attribute of the @PathVariable
annotation.
(3) You can use the todoId obtained from path variable to update the Todo resource in completed status.
HTTP status returned “200 OK” and JSON of the modified Todo resource displays in [Body] of [RESPONSE]
part.
finished is updated to true.
2162 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/java/todo/api/todo/TodoRestController.java
package todo.api.todo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.http.HttpStatus;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import todo.domain.model.Todo;
import todo.domain.service.todo.TodoService;
@RestController
@RequestMapping("todos")
public class TodoRestController {
@Inject
TodoService todoService;
@Inject
Mapper beanMapper;
@RequestMapping(method = RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
2164 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@RequestMapping(method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public TodoResource postTodos(@RequestBody @Validated TodoResource todoResource) {
Todo createdTodo = todoService.create(beanMapper.map(todoResource, Todo.class));
TodoResource createdTodoResponse = beanMapper.map(createdTodo, TodoResource.class);
return createdTodoResponse;
}
Sr. No Description
(1) In order to get the todoId from path, specify the path variable in the value attribute of the
@RequestMapping annotation.
Set the RequestMethod.DELETE to method attribute for handling the DELETE request.
(2) Specify @ResponseStatus annotation to the HTTP status code for response.
To set “204 No Content” as a HTTP status, set the HttpStatus.NO_CONTENT to the value
attribute.
(3) The type of return value is a void because there is no content to be returned in the case of DELETE.
(4) You can use the todoId obtained from path variable to delete the Todo resource.
2166 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Open the DHC, enter "localhost:8080/todo/api/v1/todos" in the URL and click the “Send” button
by specify GET in method.
you can confirm that the Todo resource has been removed.
In this tutorial, for easy understanding, the implementation of exception handling made a simpler than that are
recommended in this guideline.
It is strongly recommended that the actual exception handling should be handled in a way described in the
RESTful Web Service.
In this tutorial, the error messages are retrieved from the property file based on error code.
Therefore, modify the implementation of the Service class as follows which is created at Tutorial (Todo
Application) before implementing the exception handling.
package todo.domain.service.todo;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
import javax.inject.Inject;
2168 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.message.ResultMessages;
import todo.domain.model.Todo;
import todo.domain.repository.todo.TodoRepository;
@Service
@Transactional
public class TodoServiceImpl implements TodoService {
@Inject
TodoRepository todoRepository;
@Override
@Transactional(readOnly = true)
public Todo findOne(String todoId) {
@Override
@Transactional(readOnly = true)
public Collection<Todo> findAll() {
return todoRepository.findAll();
}
@Override
public Todo create(Todo todo) {
long unfinishedCount = todoRepository.countByFinished(false);
if (unfinishedCount >= MAX_UNFINISHED_COUNT) {
ResultMessages messages = ResultMessages.error();
messages.add("E001", MAX_UNFINISHED_COUNT);
throw new BusinessException(messages);
}
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.create(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo);
REMOVE THIS LINE IF YOU USE JPA */
return todo;
}
@Override
public Todo finish(String todoId) {
Todo todo = findOne(todoId);
if (todo.isFinished()) {
ResultMessages messages = ResultMessages.error();
messages.add("E002", todoId);
throw new BusinessException(messages);
}
todo.setFinished(true);
todoRepository.update(todo);
/* REMOVE THIS LINE IF YOU USE JPA
todoRepository.save(todo);
2170 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
In this tutorial, the error messages are retrieved from the property file based on error code.
Therefore, define the error code corresponding to the error messages in the message property file before
implementing the exception handling.
Define the error code corresponding to the error messages of the processing result in the message property file.
src/main/resources/i18n/application-messages.properties
# typemismatch
typeMismatch="{0}" is invalid.
typeMismatch.int="{0}" must be an integer.
typeMismatch.double="{0}" must be a double.
typeMismatch.float="{0}" must be a float.
typeMismatch.long="{0}" must be a long.
typeMismatch.short="{0}" must be a short.
typeMismatch.boolean="{0}" must be a boolean.
typeMismatch.java.lang.Integer="{0}" must be an integer.
typeMismatch.java.lang.Double="{0}" must be a double.
typeMismatch.java.lang.Float="{0}" must be a float.
typeMismatch.java.lang.Long="{0}" must be a long.
typeMismatch.java.lang.Short="{0}" must be a short.
typeMismatch.java.lang.Boolean="{0}" is not a boolean.
typeMismatch.java.util.Date="{0}" is not a date.
typeMismatch.java.lang.Enum="{0}" is not a valid value.
Define the error messages corresponding to input check error codes, in Bean Validation message properties file.
Change the default message definition because the default message does not include the item name in the
message
In this tutorial, only define the message corresponding to the rules (@NotNull and @Size) that are used in
TodoResource class.
src/main/resources/ValidationMessages.properties
2172 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
Create ApiError class under the todo.api.common.error package for holding the error information
generated by the REST API.
ApiError class converted into JSON and return to client.
src/main/java/todo/api/common/error/ApiError.java
package todo.api.common.error;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
2174 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final String target;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private final List<ApiError> details = new ArrayList<>();
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
MessageSource messageSource;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Object responseBody = body;
if (body == null) {
responseBody = createApiError(request, "E999", ex.getMessage());
}
return ResponseEntity.status(status).headers(headers).body(responseBody);
}
By performing the above implementation, the error information is logged in to HTTP response BODY which was
handled by the ResponseEntityExceptionHandler .
About the exception handled by ResponseEntityExceptionHandler , refer HTTP response code set by
2176 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
DefaultHandlerExceptionResolver.
HTTP status returned “405 Method Not Allowed” and the JSON error information displays in [Body] of [RE-
SPONSE] part.
• org.springframework.web.bind.MethodArgumentNotValidException
• org.springframework.validation.BindException
• org.springframework.http.converter.HttpMessageNotReadableException
• org.springframework.beans.TypeMismatchException
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
MessageSource messageSource;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Object responseBody = body;
if (body == null) {
responseBody = createApiError(request, "E999", ex.getMessage());
2178 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
}
return ResponseEntity.status(status).headers(headers).body(responseBody);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ApiError apiError = createApiError(request, "E400");
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
apiError.addDetail(createApiError(request, fieldError, fieldError
.getField()));
}
for (ObjectError objectError : ex.getBindingResult().getGlobalErrors()) {
apiError.addDetail(createApiError(request, objectError, objectError
.getObjectName()));
}
return handleExceptionInternal(ex, apiError, headers, status, request);
}
{
"todoTitle": null
Furthermore, Add HTTP header by [+] button of [REQUEST] [HEADERS] and click “Send” button after setting
[application/json] in the [Content-Type].
HTTP status returned “400 Bad Request” and JSON error information displays in [Body] of [RESPONSE] part.
Since todoTitle is required field, required error occurred.
2180 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResultMessagesNotificationException;
import org.terasoluna.gfw.common.message.ResultMessage;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
MessageSource messageSource;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Object responseBody = body;
if (body == null) {
responseBody = createApiError(request, "E999", ex.getMessage());
}
return ResponseEntity.status(status).headers(headers).body(responseBody);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ApiError apiError = createApiError(request, "E400");
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex,
WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
2182 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
HTTP status returned “409 Conflict” as a response of the 2nd request and JSON error information displays in
[Body] of [RESPONSE] part.
Set “404 NotFound” in HTTP status if Resource not found exception occurred.
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.exception.ResultMessagesNotificationException;
import org.terasoluna.gfw.common.message.ResultMessage;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
MessageSource messageSource;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Object responseBody = body;
if (body == null) {
responseBody = createApiError(request, "E999", ex.getMessage());
}
return ResponseEntity.status(status).headers(headers).body(responseBody);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ApiError apiError = createApiError(request, "E400");
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
apiError.addDetail(createApiError(request, fieldError, fieldError
.getField()));
}
for (ObjectError objectError : ex.getBindingResult().getGlobalErrors()) {
apiError.addDetail(createApiError(request, objectError, objectError
.getObjectName()));
}
2184 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex,
WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.NOT_FOUND, request);
}
HTTP status returned “404 Not Found” and JSON error information displays in [Body] of [RESPONSE] part.
src/main/java/todo/api/common/error/RestGlobalExceptionHandler.java
package todo.api.common.error;
import javax.inject.Inject;
import org.springframework.context.MessageSource;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
2186 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import org.terasoluna.gfw.common.exception.ResultMessagesNotificationException;
import org.terasoluna.gfw.common.message.ResultMessage;
@ControllerAdvice
public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Inject
MessageSource messageSource;
@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex,
Object body, HttpHeaders headers, HttpStatus status,
WebRequest request) {
Object responseBody = body;
if (body == null) {
responseBody = createApiError(request, "E999", ex.getMessage());
}
return ResponseEntity.status(status).headers(headers).body(responseBody);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
ApiError apiError = createApiError(request, "E400");
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
apiError.addDetail(createApiError(request, fieldError, fieldError
.getField()));
}
for (ObjectError objectError : ex.getBindingResult().getGlobalErrors()) {
apiError.addDetail(createApiError(request, objectError, objectError
.getObjectName()));
}
return handleExceptionInternal(ex, apiError, headers, status, request);
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<Object> handleBusinessException(BusinessException ex,
WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.CONFLICT, request);
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Object> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
return handleResultMessagesNotificationException(ex, new HttpHeaders(),
HttpStatus.NOT_FOUND, request);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleSystemError(Exception ex,
WebRequest request) {
ApiError apiError = createApiError(request, "E500");
return handleExceptionInternal(ex, apiError, new HttpHeaders(),
HttpStatus.INTERNAL_SERVER_ERROR, request);
}
2188 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
src/main/resources/META-INF/spring/todo-infra.properties
database=H2
#database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1;INIT=create table if not exists todo(todo_id varc
database.url=jdbc:h2:mem:todo;DB_CLOSE_DELAY=-1
database.username=sa
database.password=
database.driverClassName=org.h2.Driver
# connection pool
cp.maxActive=96
cp.maxIdle=16
cp.minIdle=0
cp.maxWait=60000
Open the DHC, enter "localhost:8080/todo/api/v1/todos/" in the URL and click the “Send” button
after specifying GET in method.
HTTP status returned “500 Internal Server Error” and JSON error information displays in [Body] of [RESPONSE]
part.
Note: In case of system error occurred, it is recommended to set a simple error message from which cause of
error can not be identified while error message returning to the client. When you set the error message from which
cause of error is identified, there is a possibility to exposes the vulnerability of the system to the client and may
cause security issues.
It is good to flush the cause of an error into error analysis log. The default setting of Blank project has been
outputting the log by ExceptionLogger provided in the common library therefore setting and implementation
for outputting the log is not required.
The cause of the system error can be understood that the Todo table is not exist.
... (omitted)
• How to develop basic RESTful Web service by TERASOLUNA Server Framework for Java (5.x)
• Implementation of Controller class that offers REST API(GET, POST, PUT, DELETE)
2190 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Here, explained how to implement the basic RESTful Web Services. To learn more about the architecture and
design guidelines etc, Refer [RESTful Web Service].
10.3.1 Introduction
Flow of learning
In this tutorial, users will learn the how to design data for the session management and the specific method of
implementation to use the session by creating a simple web application. This tutorial will be implemented with
the following flow.
2. To check the method of implementation for the Controller and the procedure for designing data to meet the
requirements
Target Readers
Test environment
2192 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Type Product
OS Windows 7
JVM Java 1.8
IDE Spring Tool Suite 3.6.4.RELEASE (hereafter called as ‘STS’)
Build Tool Apache Maven 3.3.3 (hereafter called as ‘Maven’)
Application Server Pivotal tc Server Developer Edition v3.1 (Included in STS)
Web Browser Google Chrome 42.0.2311.90 m
Overview
Create a simple EC site. User can perform the following operations on the EC site.
• Create an account
Application overview is shown in the following diagram. XxxPages in the diagram indicate a set of screens. In
this tutorial, the interaction between the system and the user performed on 1 screen set is handled as 1 use case.
Requirements
Functional requirements
Implement the following function for each screen (use case) described earlier in this application.
Some functions have been created in advance in the project which are offered as the initial material for this tutorial.
This is done to reduce the cost to create the part that is not directly related to the session management.
Create unfinished function in this tutorial. Further, implementation of domain layer/infrastructure layer has been
created in unfinished function as well. Therefore, create screens for unfinished function and application layer in
this tutorial.
Non-functional requirements
It is necessary to design and implement the application by considering the non-functional requirements required of
that system while creating the real application. Design/create the application in this tutorial with the assumption
of non-functional requirements in this tutorial. The specific numerical values for each requirement shown below
are the hypothetical values used for learning. It should be noted that it cannot be guaranteed that the application
2194 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Availability
• Utilization: 99%
Usability
Performance
• The number of users/number of concurrent access users/number of online process records together are
expected to increase 1.2 times in 1 year
It is necessary to consider the above requirements while reviewing the following items to design the session
management.
Further, passing of important information including personal information/credit card information also should be
considered in the design of the session management other than the items mentioned above.
Configuration of platform
Application created in this tutorial is to be operated on the following platforms. The specific numerical values for
the configuration shown below are the hypothetical values used for learning.
It is necessary to consider the above configuration while reviewing the memory utilization and replication status
to design the session management.
Determine the policy to create the application based on the requirements described earlier. Since the domain
layer/infrastructure layer have been created in this tutorial, consider only the items related to the application layer
as the target. Further, since this tutorial aims at learning the method to use the session, description for the items
those are not directly related to the session management is omitted.
Warning: Note that an example of the process that uses the session is shown in this chapter. It is required to
follow the work instructions/operating procedures for each project in the real development.
Screen definition
Define the screen on which the application is displayed, based on the requirements. Details for the screen definition
process are omitted.
Image for the screen to be created in this tutorial defined in the end, is as follows.
Some of the transitions are given below which are omitted in the above diagram.
• When the user logins from the Login screen, the transition takes place to screen (5)
• When ‘Home’ button is clicked on each screen of Account Update Pages, the transition takes place to
screen (5)
2196 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
• When ‘Update Account’ button is clicked on each screen of Item View Pages, Cart View Pages and Order
Pages, the transition takes place to screen (1)
• When ‘Logout’ button is clicked on each screen of Item View Pages, Cart View Pages and Order Pages, the
transition takes place to Login screen
Extracting URL
The application determines the URL that does processing, based on the screen image.
Set the URL and parameters for each event occurred from each screen. Assign the respective names as per the
following standards.
• Parameter:?<Process name>
Since the use cases are divided into account creation and update in this application, set the URLs as /account/create
and /account/update respectively.
Further, also determine the Controller to process each URL. Basically process 1 use case in 1 Controller.
Finally the extracted URL can be arranged as follows. The Controller that is mentioned as “Created”, exists in the
project provided as the initial material. Further, the process wherein the path mentioned as “Created” is accessed,
is already described within the created Controller mentioned earlier.
2198 10 Tutorials
Design the input/output data handled by the application based on the screen image.
Extracting data
Extract the input/output data handled by the application screen. The following data can be extracted based on the
screen image mentioned earlier.
(1) Update account Account name, mail address, birthdate, postal code, address, card number,
information validity, security code
(2) Account information Account name, mail address, password, birthdate, postal code, address,
card number, validity, security code
(4) Product information Product name, unit price, description, (product ID)
(6) Cart information Product name, unit price, quantity, (product ID)
(8) Order information Order ID, order date-time, (account ID), product name, unit price, quantity
Defining lifecycle
Define the lifecycle of the data extracted in the previous paragraph. Determine when the data will be generated
and when it will be discarded in the definition of the lifecycle.
Also note that the data which is to be retained in multiple screens will have multiple discard timings as below.
If the above precautions are considered, the lifecycle of the data extracted in the previous paragraph can be defined
as follows.
2200 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Update account Generate the data with the input from screen (1) and retain it during the
information transition from (1) to (3). Discard it when there is a transition except from
screen (1) to (3).
(2) Account information Generate the data at the time of login and discard at the time of logout.
(3) Product search Generate the data when there is a transition to screen (5) and retain during
information the transition from (1) to (8). Discard when there is a transition to screen
(9).
(4) Product information Generate the data when there is a transition to screen (5) or (6) and retain
only during that request.
(5) Register cart information Generate the data by the input from screen (5) or (6) and retain only
during that request.
(6) Cart information Generate an empty object when there is a transition to screen (5) and
retain during the transition from (1) to (8). Discard when there is a
transition to screen (9).
(7) Delete cart information Generate the data by the input from screen (7) and retain only during that
request.
(8) Order information Generate the data when there is a transition to screen (9) and retain only
during that request.
When the information needs to be retained on multiple screens, implementation is easy by using a session.
At the same time, when the session is used, its demerits also need to be considered. Refer to the guideline
:doc:’../ArchitectureInDetail/WebApplicationDetail/SessionManagement’ and determine whether to use the ses-
sion in this tutorial.
It is described in the guideline that it is recommended to use the session first and to store only the data that is
necessary, in the session. This tutorial also considers not using the session.
2202 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Update account The data needs to be passed using hidden since the Update account information is to be
information retained across 3 screens. However, the Update account information contains the
important information like the card number. It is a security issue since the important
information is written in the HTML source without masking the important information
while passing data using hidden. Therefore, consider using the session in this tutorial.
Account information Since it is retained on all screens after login, the data needs to be passed using hidden. The
process of passing data must be described on almost all screens created in this case.
Therefore, consider using the session in this tutorial to reduce the implementation cost for
screens as well.
Product search The data needs to be passed using hidden since the Product search information is to be
information retained across 8 screens. The process of passing data must be described on almost all
screens created in this case. Therefore, consider using the session in this tutorial to reduce
the implementation cost for screens as well.
Product information Since the Delete cart information is used only in 1 screen, the data should be handled in
the request scope.
Register cart information Since the Delete cart information is used only in 1 screen, the data should be handled in
the request scope.
Cart information The data needs to be passed using hidden since the Cart information is to be retained
across 8 screens. The process of passing data must be described on almost all screens
created in this case. Therefore, consider using the session in this tutorial to reduce the
implementation cost for screens as well.
Delete cart information Since the Delete cart information is used only in 1 screen, the data should be handled in
the request scope.
Order information Since the Order information is used only in 1 screen, the data should be handled in the
10.3. Session tutorial 2203
request scope.
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
From the above-mentioned, consider using the session for these 4 i.e. the Update account information, Account
information, Cart information and Product search information.
Next, verify the demerits of using the session. According to this verification, if it is determined that the impact of
demerits cannot be ignored, do not use the session.
The following 3 major points can be mentioned as the demerits of using the session.
• When it is used on multiple tabs and multiple browsers, (it needs to be considered that) the integrity of data
may be lost because of mutual operations.
• Since it is managed on the memory, the memory is likely to be exhausted because of the size of the data to
be managed.
• The session replication needs to be considered when AP server multiplexing is done with the aim to imple-
ment scale-out or to achieve high availability. At that time, if large data is handled in the session, it may
affect the performance etc.
Consider how to handle the respective risks or whether to allow the risks regarding the above-mentioned point of
view.
2204 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Data integrity The operation on multiple browsers and tabs is not guaranteed in this application.
Therefore, a countermeasure to secure the data integrity is not required.
Memory utilization Estimate the data size for which usage of session is considered. Assume maximum 100
characters 240 bytes (4 characters 8 bytes + initial 40 bytes) for the character string
element, 24 bytes for data element and 16 bytes for the numerical value element. Further,
the authentication information stored in the session at the time of login authentication also
contains the size of “UserDetails”. “UserDetails” broadly contains ID, password and user
rights. Multiple user rights can be specified, but assume here as 1. The result estimated for
each item is as follows.
1 user uses maximum 19KB in total. 1 user uses approximately 21KB if safety factor of
10% is considered. Since the usage is about 210MB even if it is considered that 10000
people are simultaneously connected and the memory load is considerably below 8GB
even if other memory utilization is considered, it is less likely that the memory exhaustion
will happen.
AP server multiplexing Since high availability is not required in this application, use case continuation when
failure happens is not required and redoing of use case because of re-login is allowed.
Therefore, only take the countermeasure to set the load balancer so that all the requests
occurred within the same session are distributed to the same AP server and do not
implement the replication among the AP servers of the session.
Warning: Tool (for example, like SizeOf) needs to be used to measure the object size for estimation of the
object size. The calculation formula in this tutorial refers the trend in actually measured values in SizeOf, but
note that ultimately it is a temporary value. It should be separately considered how to calculate it at the time
of sizing in the real system development.
Warning: The data to be stored in the session is basically restricted to the input data to avoid memory
exhaustion. Since the size of the output data for search results tends to increase and on the other hand, often it
is read-only that cannot be edited using screen operations, it is not suitable for storing in the session.
Increase in the management cost of session key is also 1 of the points to be considered other than the above-
mentioned. However, since the data quantity to be stored in the session is not large in the application created this
time, it can be said that the management cost of the session key is limited.
It can be said from this result that the impact of demerits happening because of using session is not large. Data to
be stored in the session finally is as follows.
• Account information
• Cart information
This tutorial concludes that passing of data is implemented using the session. However, it is also considered as
a result of the investigation that it concludes that session should not be used. Implement passing the data using
hidden as an example when the session is not used.
Further, when the session is used, sometimes method to maintain the data integrity and replication settings are
required.
The guideline mentions a method to avoid it using transaction token check. However, note that it becomes a low-
usability application in this case. Double Submit Protection should be referred for the specific implementation
method.
Since replication settings depend on AP server, when replication needs to be considered, configuration of AP
server needs to be checked.
Warning: Sometimes there exists a data to be stored in the session other than the data determined here.
Session is used when the following items among the items in the guideline are used.
• It uses authentication/authorization/CSRF countermeasures using Spring Security
• It uses transaction token check for prevention of double transmission
Method of implementation to use the data during the session for each data is determined in this section.
The guideline provides 2 implementation methods corresponding to the locations of using data. Session Manage-
ment classifies the methods to be used depending on whether the data is contained within 1 Controller. Therefore,
the implementation method needs to be decided considering the lifecycle of data to be stored in the session and
2206 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
URL mapping. Further, when the data is associated with the authentication information, session management
should be implemented by the Spring Security function.
Considering these, the final result for which the data handled in the session is organized is as follows.
Change account information Used only within 1 Controller Method wherein @SessionAttributes
annotation is used
Account information Used among multiple Method wherein Spring Security function
Controllers is used
Used in authentication process
Product search information Used among multiple Method wherein Bean for session scope of
Controllers Spring is used
Cart information Used among multiple Method wherein Bean for session scope of
Controllers Spring is used
Account information is already created in the project provided as initial material and is managed using
the Spring Security function. Therefore, this tutorial does not describe any specific method of using.
:doc:’../Security/Authentication’ should be referred for the specific method of using.
Items mentioned hereafter need to be considered when it is decided to use the session. Review the respective
items.
Session synchronization
Object stored in the session can be simultaneously accessed by means of multiple requests of the same user.
Therefore, when session synchronization is not performed, it can cause unexpected error or operation.
Since the guideline mentions a method of implementing synchronization where BeanProcessor is used in Session
Management, use this in this tutorial.
Session time out needs to be set when session is used. If timeout duration is too long, unnecessary resources are
retained in the memory and if timeout duration is too small, user friendliness is lowered. Therefore, appropriate
time needs to be set as per the requirement.
This tutorial is also well-equipped with memory resources, set the default value of AP server to 30 minutes.
Further, handling the requests after session timeout also needs to be reviewed. The guideline mentions a method
to handle the requests after session timeout in Session Management.
Settings are done so as to transit to the login screen after timeout in this tutorial.
Project creation
As stated already, this tutorial starts with the status that some functions are created. Therefore, proceed with
development using already created project.
2208 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
1. Access to tutorial-apps.
2. Click ‘Branch’ button to select Branch of the required version and click ‘Download ZIP’ button to download
zip file
Note that since the method to import the project is described in Tutorial (Todo Application), the description is
omitted in this tutorial.
Project configuration
It states about the configuration of the initial project fetched using git. Only the differences between the project
fetched and the blank project are shown below.
session-tutorial-init-domain
└-- src
└-- main
├-- java
| └-- com
| └-- example
| └-- session
| └-- domain
| ├-- model ... (1)
| | ├-- Account.java ... (2)
| | ├-- Cart.java ... (3)
| | ├-- CartItem.java ... (3)
| | ├-- Goods.java
| | ├-- Order.java ... (4)
| | └-- OrderLine.java ... (4)
| ├-- repository ... (5)
| | ├-- account
| | | └-- AccountRepository.java
| | ├-- goods
| | | └-- GoodsRepository.java
| | └-- order
| | └-- OrderRepository.java
| └-- service ... (6)
| ├-- account
| | └-- AccountService.java
| ├-- goods
| | └-- GoodsService.java
| ├-- order
| | ├-- EmptyCartOrderException.java
| | ├-- InvalidCartOrderException.java
| | └-- OrderService.java
| └-- userdetails
| ├-- AccountDetails.java
| └-- AccountDetailsService.java
└-- resources
├-- com
| └-- example
| └-- session
| └-- domain
| └-- repository ... (7)
| ├-- account
| | └-- AccountRepository.xml
| ├-- goods
| | └-- GoodsRepository.xml
| └-- order
| └-- OrderRepository.xml
└-- META-INF
├-- dozer
| └-- order-mapping.xml ... (8)
└-- spring
└-- session-tutorial-init-codelist.xml ... (9)
2210 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(9) Bean definition file in which code list used in this application is defined.
session-tutorial-init-env
└-- src
└-- main
└-- resources
(1) Directory to store SQL in order to setup in-memory database (H2 Database) in this
application.
session-tutorial-init-web
└-- src
└-- main
├-- java
| └-- com
| └-- example
| └-- session
| └-- app ... (1)
| ├-- account
| | ├-- AccountCreateController.java
| | ├-- AccountCreateForm.java
| | ├-- IlleagalOperationException.java
| | └-- IlleagalOperationExceptionHandler.java
| ├-- goods
| | ├-- GoodsController.java
| | └-- GoodsViewForm.java
| ├-- login
| | └-- LoginController.java
| └-- validation
| ├-- Confirm.java
| └-- ConfirmValidator.java
├-- resources
| ├-- i18n
| | └-- application-messages.properties ... (2)
| ├-- META-INF
| | └-- spring ... (3)
| | ├-- spring-mvc.xml
| | └-- spring-security.xml
| └-- ValidationMessages.properties ... (2)
└-- webapp
├-- resources ... (4)
| ├-- app
| | └-- css
| | └-- styles.css
| └-- vendor
| └-- bootstrap-3.0.0
| └-- css
| └-- bootstrap.css
└-- WEB-INF
└-- views ... (5)
2212 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
├-- account
| ├-- createConfirm.jsp
| ├-- createFinish.jsp
| └-- createForm.jsp
├-- common
| ├-- error
| | └-- illegalOperationError.jsp
| └-- include.jsp
├-- goods
| ├-- showGoods.jsp
| └-- showGoodsDetails.jsp
└-- login
└-- loginForm.jsp
(1) Package to store the class in application layer used in this application.
(3) Bean definition file in which component used in this application is defined
Operation verification
Check the operation of project fetched before the application development. Start the application server with the
project imported in STS as the target The method to start the application server is omitted in this tutorial since it
is described in Tutorial (Todo Application).
Account can be created when “here” link on the login screen is selected.
When (E-mail=”a@b.com”, Password=”demo”) is input in the form on the login screen, login can be done. Prod-
uct list is displayed after the login. The product details can be displayed when the product name is selected.
Create a function that allows the user to input the information and updates the account information.
Manage the Change account information using “@SessionAttributes annotation” as described in Application de-
sign.
The information of the screen implemented in the Change account information function is shown below.
2214 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
“/session-tutorial-init-web/src/main/java/com/example/session/app/account/AccountUpdateForm.java”
package com.example.session.app.account;
import java.io.Serializable;
import java.util.Date;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.Email;
import org.springframework.format.annotation.DateTimeFormat;
/**
*
*/
private static final long serialVersionUID = 1L;
// (2)
@NotNull(groups = { Wizard1.class })
@Size(min = 1, max = 255, groups = { Wizard1.class })
private String name;
@NotNull(groups = { Wizard1.class })
@Size(min = 1, max = 255, groups = { Wizard1.class })
@Email(groups = { Wizard1.class })
private String email;
@NotNull(groups = { Wizard1.class })
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private Date birthday;
@NotNull(groups = { Wizard1.class })
@Size(min = 7, max = 7, groups = { Wizard1.class })
private String zip;
@NotNull(groups = { Wizard1.class })
@Size(min = 1, max = 255, groups = { Wizard1.class })
private String address;
2216 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@DateTimeFormat(pattern = "yyyy-MM")
private Date cardExpirationDate;
this.address = address;
}
}
}
2218 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Implement Serializable in advance to store the instance of this class in the session.
(2) Make validation groups to specify the target of the input check for each screen transition.
In the example above, 2 groups are created implement the input check corresponding to the input item
on the 1st page and the input item on the 2nd page respectively.
Creating Controller
Create the Controller. The description in which the form that receives the input information is managed using
“@SessionAttributes” annotation is required in the Controller.
“/session-tutorial-init-web/src/main/java/com/example/session/app/account/AccountUpdateController.java”
package com.example.session.app.account;
import javax.inject.Inject;
import org.dozer.Mapper;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.session.app.account.AccountUpdateForm.Wizard1;
import com.example.session.app.account.AccountUpdateForm.Wizard2;
import com.example.session.domain.model.Account;
import com.example.session.domain.service.account.AccountService;
import com.example.session.domain.service.userdetails.AccountDetails;
@Controller
@SessionAttributes(value = { "accountUpdateForm" }) // (1)
@RequestMapping("account")
public class AccountUpdateController {
@Inject
AccountService accountService;
@Inject
Mapper beanMapper;
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
return "account/updateForm1";
}
if (result.hasErrors()) {
return "account/updateForm1";
}
return "account/updateForm2";
}
2220 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
if (result.hasErrors()) {
return "account/updateForm2";
}
return "account/updateConfirm";
}
if (result.hasErrors()) {
ResultMessages messages = ResultMessages.error();
messages.add("e.st.ac.5001");
throw new IllegalOperationException(messages);
}
return "redirect:/account/update?finish";
}
(1) Specify the attribute name of the object to be stored in the session, in the value attribute of
“@SessionAttributes” annotation.
The object having the attribute name "accountUpdateForm" is stored in the session in the above
example.
(2) Specify the attribute name to be stored in the Model object in the value attribute.
The returned object is stored in the session by the attribute name as "accountUpdateForm" in
the above example.
Since the “@ModelAttribute” annotated method can no longer be called by the request after the
object is stored in the session when the value attribute is specified, it has a merit that unnecessary
object is not generated.
(3) In order to use the object managed by “@SessionAttributes” annotation, add argument in the method
so that the object can be received.
Use “@Validated” annotation when the input check is required.
The object that contains "accountUpdateForm" that is the default attribute name of
“AccountUpdateForm”, in the attribute name is passed as an argument in the above example.
(4) Call “setComplete” method of “SessionStatus” object and delete the object from the session.
Warning: The object managed using “@SessionAttributes” annotation continues to remain in the session
unless it is explicitly deleted. Therefore, the data retained even when the transition has taken place outside
the screen handled by the Controller and returned again, can be browsed. The data that is no longer required,
should always be deleted to avoid the memory exhaustion.
Warning: When the user goes back using the browser button, inputs the URL directly and moves from
one screen to another, it is required to note the point that “setComplete” method is not called and the session
remains active without being cleared.
Creating JSP
Create a screen to pass data to the form object managed by “@SessionAttributes” annotation.
2222 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/account/updateForm1.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Account Update Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<%-- (1) --%>
<form:form action="${pageContext.request.contextPath}/account/update"
method="post" modelAttribute="accountUpdateForm">
<td> </td>
<td><input type="submit" name="form2" id="next" value="next" /></td>
</tr>
</table>
</form:form>
<form method="get"
action="${pageContext.request.contextPath}/account/update">
<input type="submit" name="home" id="home" value="home" />
</form>
</div>
</body>
</html>
(1) Specify the attribute name of the form object that receives the input data, in modelAttribute.
The object with the attribute name "accountUpdateForm" receives the input data in the above
example.
(2) Specify the element name of the object that stores the input data in path attribute of form:input tag.
If this method is used, when data already exists in the element name of the specified object, that value
becomes as the default value of the input form.
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/account/updateForm2.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Account Update Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<form:form action="${pageContext.request.contextPath}/account/update"
method="post" modelAttribute="accountUpdateForm">
2224 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<form method="get"
action="${pageContext.request.contextPath}/account/update">
<input type="submit" name="home" id="home" value="home" />
</form>
</div>
</body>
</html>
Confirmation screen
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/account/updateConfirm.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Account Update Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<form:form action="${pageContext.request.contextPath}/account/update"
method="post">
2226 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
</tr>
</table>
</form:form>
<form method="get"
action="${pageContext.request.contextPath}/account/update">
<input type="submit" name="home" id="home" value="home" />
</form>
</div>
</body>
</html>
Completion screen
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/account/updateFinish.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Account Update Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<tr>
<td><label for="cardNumber">your card number</label></td>
<td><span id="cardNumber">****-****-****-${f:h(account.lastFourOfCardNumber)}</spa
</tr>
<tr>
<td><label for="cardExpirationDate">expiration date of
your card</label></td>
<td><span id="cardExpirationDate"><fmt:formatDate
value="${account.cardExpirationDate}" pattern="yyyy-MM" /></span></td>
</tr>
<tr>
<td><label for="cardSecurityCode">security code of your
card</label></td>
<td><span id="cardSecurityCode">${f:h(account.cardSecurityCode)}</span></td>
</tr>
</table>
<form method="get"
action="${pageContext.request.contextPath}/account/update">
<input type="submit" name="home" id="home" value="home" />
</form>
</div>
</body>
</html>
Checking operation
The account information can be updated using the implementation so far. The transition takes place to the “Update
account information” screen by clicking ‘Account Update’ button in the upper part of the product list display
screen. At present, the information of account to which the user is logged in, is displayed in the form as the initial
value. Finally the account information is updated when the user changes the form value and proceeds to the next
screen.
Since the form that receives the input value is stored in the session with the implementation so far, passing of data
can be easily implemented. Further, the changed information is reset if the transition takes place to the Update
account information screen after clicking ‘home’ button since the session is discarded when ‘home’ button is
clicked.
Create a function to register the products for a spe cified quantity in the cart.
Manage the cart information as a Bean for session scope as explained in Application design.
The information of screen implemented in cart item registration function is shown below.
2228 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Process to add a product to the POST /addToCart Redirect to Product list screen
cart display process
Object to retain the cart information is already created as “Cart.java”. Therefore, add the settings so as to enable
handling this object as the Bean for session scope.
There are 2 types of configuration methods mentioned in Session Management as the methods to use the Bean for
session scope. Define the Bean using component-scan in this tutorial.
Warning: The target object needs to be ‘Serializable’ to register as the Bean for session scope
To define the Bean for session scope using component-scan, the following annotation should be added to the class
to be registered as Bean.
“/session-tutorial-init-domain/src/main/java/com/example/session/domain/model/Cart.java”
package com.example.session.domain.model;
import java.io.Serializable;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.stereotype.Component;
import org.springframework.util.SerializationUtils;
@Component // (1)
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) // (2)
public class Cart implements Serializable {
// omitted
(1) Specify “@Component” annotation so that it becomes the target for component-scan.
Further, the base-package that is a target for component-scan, needs to be specified in the Bean definition file.
However, since the following is already mentioned in the Bean definition file created in this tutorial, it is not
required to add a new description.
“/session-tutorial-init-domain/src/main/resources/META-INF/spring/session-tutorial-init-domain.xml”
“/session-tutorial-init-web/src/main/java/com/example/session/app/goods/GoodAddForm.java”
package com.example.session.app.goods;
import java.io.Serializable;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
/**
*
*/
private static final long serialVersionUID = 1L;
@NotNull
private String goodsId;
@NotNull
2230 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@Min(1)
private int quantity;
Creating Controller
Since it is already created to process the partial request, the following code is added.
“/session-tutorial-init-web/src/main/java/com/example/session/app/goods/GoodsController.java”
package com.example.session.app.goods;
import javax.inject.Inject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.session.domain.model.Cart;
import com.example.session.domain.model.CartItem;
import com.example.session.domain.model.Goods;
import com.example.session.domain.service.goods.GoodsService;
@Controller
@RequestMapping("goods")
public class GoodsController {
@Inject
GoodsService goodsService;
// (1)
@Inject
Cart cart;
@ModelAttribute(value = "goodViewForm")
public GoodViewForm setUpCategoryId() {
return new GoodViewForm();
}
return "/goods/showGoodsDetail";
}
if (result.hasErrors()) {
ResultMessages messages = ResultMessages.error()
.add("e.st.go.5001");
attributes.addFlashAttribute(messages);
return "redirect:/goods";
}
2232 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
return "redirect:/goods";
}
}
Creating JSP
Since JSP is also already created, the code shown below is added at the end of body tag.
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/goods/showGoods.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Item List Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/vendor/bootstrap-3.0.0/css/bootstrap.css"
type="text/css" media="screen, projection">
</head>
<body>
<br>
<div class="container">
<p>select a category</p>
<form:form method="get"
action="${pageContext.request.contextPath}/goods/"
modelAttribute="goodViewForm">
<form:select path="categoryId" items="${CL_CATEGORIES}" />
<input type="submit" id="update" value="update" />
</form:form>
<br />
<t:messagesPanel />
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
</tr>
<c:forEach items="${page.content}" var="goods" varStatus="status">
<tr>
<td><a id="${f:h(goods.name)}"
href="${pageContext.request.contextPath}/goods/${f:h(goods.id)}">${f:h(goo
<td><fmt:formatNumber value="${f:h(goods.price)}"
type="CURRENCY" currencySymbol="¥" maxFractionDigits="0" /></td>
<td><form:form method="post"
action="${pageContext.request.contextPath}/goods/addToCart"
modelAttribute="goodAddForm">
<input type="text" name="quantity" id="quantity${status.index}" value=
<input type="hidden" name="goodsId" value="${f:h(goods.id)}" />
<input type="submit" id="add${status.index}" value="add" />
</form:form></td>
</tr>
</c:forEach>
</table>
<t:pagination page="${page}" outerElementClass="pagination" />
</div>
<div>
<p>
<fmt:formatNumber value="${page.totalElements}" />
results <br> ${f:h(page.number + 1) } / ${f:h(page.totalPages)}
Pages
</p>
</div>
<div>
<%-- (1) --%>
<spring:eval var="cart" expression="@cart" />
<form method="get" action="${pageContext.request.contextPath}/cart">
<input type="submit" id="viewCart" value="view cart" />
</form>
<table>
2234 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
</body>
</html>
(1) Store the Bean in a variable in order to display the contents of the Bean for session scope in a screen.
Cart object in session scope is stored in variable “cart” in the above example.
(2) Browse the contents of the Bean for session scope through the variable created in (1).
The contents of the Bean for session scope are browsed through variable “var” in the above example.
Note: var attribute is not required if simply the contents of Bean are to be displayed alone without storing them
in a variable. It can be displayed using “<spring:eval expression=”@cart” />” in the above example.
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/goods/showGoodsDetail.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Item List Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div class="container">
<table>
<tr>
<th>Name</th>
<td>${f:h(goods.name)}</td>
</tr>
<tr>
<th>Price</th>
<td><fmt:formatNumber value="${f:h(goods.price)}"
type="CURRENCY" currencySymbol="¥" maxFractionDigits="0" /></td>
</tr>
<tr>
<th>Description</th>
<td>${f:h(goods.description)}</td>
</tr>
</table>
<form:form method="post"
action="${pageContext.request.contextPath}/goods/addToCart"
modelAttribute="AddToCartForm">
Quantity<input type="text" id="quantity" name="quantity"
value="1" />
<input type="hidden" name="goodsId" value="${f:h(goods.id)}" />
<input type="submit" id="add" value="add" />
</form:form>
2236 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Checking operation
Products can be registered to the cart with the implementation so far. Contents of the cart on the same page are
displayed by clicking ‘add’ button for a product on the product list display screen.
Since the cart object is stored in the session with the implementation so far, cart information is saved even though
moved to the account information update screen and returned.
Products can be added to the cart with the implementation so far. However, screen where the transition after
adding products takes place, is usually the 1st page of ‘book’ category.
This tutorial has a specification to retain the product search information including the selection category and page
number till the order is completed. Therefore, modify the implementation so as to transit to the previous status
after adding products or when returned from the account update screen.
Manage the product search information as the Bean for session scope as explained in Application design.
Create the session scope Bean to retain the product search information. Define a bean using component-scan
similar to the cart information.
“/session-tutorial-init-web/src/main/java/com/example/session/app/goods/GoodsSearchCriteria.java”
package com.example.session.app.goods;
import java.io.Serializable;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component // (1)
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) // (2)
public class GoodsSearchCriteria implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
2238 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
(1) Specify “@Component” annotation so that it becomes the target for component-scan
Further, it is required to specify base-package that is a target for component-scan in Bean definition file. However,
since the following is already mentioned in the Bean definition file created in this tutorial, it is not required to add
a new description.
“/session-tutorial-init-web/src/main/resources/META-INF/spring/spring-mvc.xml”
Modifying Controller
Modify the Controller so as to retain the product search information in the session and to use the product search
information retained in the session.
“/session-tutorial-init-web/src/main/java/com/example/session/app/goods/GoodsController.java”
package com.example.session.app.goods;
import javax.inject.Inject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.session.domain.model.Cart;
import com.example.session.domain.model.CartItem;
import com.example.session.domain.model.Goods;
import com.example.session.domain.service.goods.GoodsService;
@Controller
@RequestMapping("goods")
public class GoodsController {
@Inject
GoodsService goodsService;
@Inject
Cart cart;
// (1)
@Inject
GoodsSearchCriteria criteria;
@ModelAttribute(value = "goodViewForm")
public GoodViewForm setUpCategoryId() {
return new GoodViewForm();
}
// (2)
@RequestMapping(value = "", method = RequestMethod.GET)
2240 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
// (3)
@RequestMapping(value = "", method = RequestMethod.GET, params = "categoryId")
String changeCategoryId(GoodViewForm form, Pageable pageable, Model model) {
criteria.setPage(pageable.getPageNumber());
criteria.setCategoryId(form.getCategoryId());
return showGoods(pageable, model);
}
// (4)
@RequestMapping(value = "", method = RequestMethod.GET, params = "page")
String changePage(GoodViewForm form, Pageable pageable, Model model) {
criteria.setPage(pageable.getPageNumber());
form.setCategoryId(criteria.getCategoryId());
return showGoods(pageable, model);
}
// (5)
String showGoods(Pageable pageable, Model model) {
Page<Goods> page = goodsService.findByCategoryId(
criteria.getCategoryId(), pageable);
model.addAttribute("page", page);
return "goods/showGoods";
}
return "/goods/showGoodsDetail";
}
if (result.hasErrors()) {
ResultMessages messages = ResultMessages.error()
.add("e.st.go.5001");
attributes.addFlashAttribute(messages);
return "redirect:/goods";
}
return "redirect:/goods";
}
}
(2) Perform the pre-processing of the usual product list screen display process. Set the product category
stored in the session to the form and the page number to “pageable”. The product category is set to
the form in order to specify the product category displayed using the select box.
(3) Perform the pre-processing of the product list screen display process when the category is changed.
Store the product category entered in the session. Specify the default 1st page of the page numbers in
“pageable”.
(4) Perform the pre-processing of the product list screen display process when the page is changed. Store
the page number entered in the session. Set the product category stored in the session in the form.
(5) Handle the common portion. Search a product based on product category managed in session and
“pageable” fetched in the preprocessing.
Checking operation
Product search information can be retained with the implementation so far. For example, when a product is added
to the cart on the 2nd page of ‘music’ category, the destination for transition remains the original 2nd page of
‘music’ category. Further, when ‘Account Update’ button is clicked on the same screen, moved to the account
update screen, ‘home’ button on the account update screen is clicked and returned, the destination for transition is
just the original 2nd page of ‘music’ category.
2242 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
The information of screen implemented using the cart item deletion function is shown below.
“/session-tutorial-init-web/src/main/java/com/example/session/app/cart/CartForm.java”
package com.example.session.app.cart;
import java.util.Set;
import org.hibernate.validator.constraints.NotEmpty;
@NotEmpty
private Set<String> removedItemsIds;
Creating Controller
Create Controller.
“/session-tutorial-init-web/src/main/java/com/example/session/app/cart/CartController.java”
package com.example.session.app.cart;
import javax.inject.Inject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.session.domain.model.Cart;
@Controller
@RequestMapping("cart")
public class CartController {
// (1)
@Inject
Cart cart;
@ModelAttribute
CartForm setUpForm() {
return new CartForm();
}
@RequestMapping(method = RequestMethod.GET)
String viewCart(Model model) {
return "cart/viewCart";
}
@RequestMapping(method = RequestMethod.POST)
String removeFromCart(@Validated CartForm cartForm,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
ResultMessages messages = ResultMessages.error()
.add("e.st.ca.5001");
model.addAttribute(messages);
return viewCart(model);
}
cart.remove(cartForm.getRemovedItemsIds()); // (2)
return "redirect:/cart";
}
}
2244 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Creating JSP
Display the cart list and create JSP to select the product to be deleted. Products can be ordered from this screen.
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/cart/viewCart.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>View Cart Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div>
<spring:eval var="cart" expression="@cart" />
<form:form method="post"
action="${pageContext.request.contextPath}/cart"
modelAttribute="cartForm">
<form:errors path="removedItemsIds" cssClass="error-messages" />
<t:messagesPanel />
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
<th>Remove</th>
</tr>
<c:forEach items="${cart.cartItems}" var="cartItem"
varStatus="status">
<tr>
<td><span id="itemName${status.index}">${f:h(cartItem.goods.name)}</span><
<td><span id="itemPrice${status.index}"><fmt:formatNumber
value="${cartItem.goods.price}" type="CURRENCY"
currencySymbol="¥" maxFractionDigits="0" /></span></td>
<td><span id="itemQuantity${status.index}">${f:h(cartItem.quantity)}</span
<%-- (1) --%>
<td><input type="checkbox" name="removedItemsIds"
id="removedItemsIds${status.index}"
value="${f:h(cartItem.goods.id)}" /></td>
</tr>
</c:forEach>
<tr>
<td>Total</td>
<td><span id="totalPrice"><fmt:formatNumber
value="${f:h(cart.totalAmount)}" type="CURRENCY"
currencySymbol="¥" maxFractionDigits="0" /></span></td>
<td></td>
<td></td>
</tr>
</table>
<input type="submit" id="remove" value="remove" />
</form:form>
</div>
2246 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Checking operation
Products registered in the cart can be deleted with the implementation so far. Transition to the cart display screen
takes place by clicking ‘viewCart’ button on the product list display screen. Product can be deleted from the cart
by checking the product to be deleted on the cart display screen and clicking ‘remove’ button.
Information of the screen implemented in the product order function is shown below.
Creating Controller
“/session-tutorial-init-web/src/main/java/com/example/session/app/order/OrderController.java”
package com.example.session.app.order;
import javax.inject.Inject;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.terasoluna.gfw.common.exception.BusinessException;
import org.terasoluna.gfw.common.message.ResultMessages;
import com.example.session.app.goods.GoodsSearchCriteria;
import com.example.session.domain.model.Cart;
import com.example.session.domain.model.Order;
import com.example.session.domain.service.order.EmptyCartOrderException;
import com.example.session.domain.service.order.InvalidCartOrderException;
import com.example.session.domain.service.order.OrderService;
import com.example.session.domain.service.userdetails.AccountDetails;
@Controller
@RequestMapping("order")
public class OrderController {
@Inject
OrderService orderService;
// (1)
@Inject
Cart cart;
@Inject
GoodsSearchCriteria criteria;
2248 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
return "order/confirm";
}
@RequestMapping(method = RequestMethod.POST)
String order(@AuthenticationPrincipal AccountDetails userDetails,
@RequestParam String signature, RedirectAttributes attributes) {
Order order = orderService.purchase(userDetails.getAccount(), cart,
signature); // (2)
attributes.addFlashAttribute(order);
criteria.clear(); // (3)
return "redirect:/order?finish";
}
// (4)
@ExceptionHandler({ EmptyCartOrderException.class,
InvalidCartOrderException.class })
@ResponseStatus(HttpStatus.CONFLICT)
ModelAndView handleOrderException(BusinessException e) {
return new ModelAndView("common/error/businessError").addObject(e
.getResultMessages());
}
}
(2) The contents of the Bean for session scope are made empty using the method of Service in the domain
layer.
Accordingly, Bean for session scope is discarded.
Further, information in the Bean for session scope is used by the screen where the user transits after
discarding Bean in this application.
Therefore, information that existed in the Bean for session scope is re-entered in a different object and
added to the Flash scope.
(4) Since a Business exception may occur in the Service method, error handling is performed in this
method.
Because of this, user transits to the specified error screen when a Business exception occurs.
Warning: Method to discard the Bean for session scope is different from the method to discard the object
managed by @SessionAttributes. Discarding the Bean for session scope should be assigned to DI container
and should not be discarded by the application. Therefore, fields in the Bean for session scope just need to be
reset to discard the Bean for session scope. Bean itself is discarded at the time of session timeout or logout.
Creating JSP
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/order/confirm.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Order Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
2250 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<body>
<div>
<spring:eval var="cart" expression="@cart" />
<table>
<tr>
<td><label for="name">name</label></td>
<td><span id="name">${f:h(account.name)}</span></td>
</tr>
<tr>
<td><label for="email">e-mail</label></td>
<td><span id="email">${f:h(account.email)}</span></td>
</tr>
<tr>
<td><label for="zip">zip</label></td>
<td><span id="zip">${f:h(account.zip)}</span></td>
</tr>
<tr>
<td><label for="address">address</label></td>
<td><span id="address">${f:h(account.address)}</span></td>
</tr>
<tr>
<%-- (1) --%>
<td>payment</td>
<td><span id="payment"><c:choose>
<c:when test="${empty account.cardNumber}">
cash
</c:when>
<c:otherwise>
card (card number : ****-****-****-${f:h(account.lastFourOfCardNumber)
</c:otherwise>
</c:choose></span></td>
</tr>
</table>
</div>
<div style="display: inline-flex">
<form:form method="post"
action="${pageContext.request.contextPath}/order">
<input type="hidden" name="signature" value="${f:h(signature)}" />
<input type="submit" id="order" value="order" />
</form:form>
<form method="get" action="${pageContext.request.contextPath}/cart">
<input type="submit" id="back" value="back" />
</form>
</div>
<div>
<form method="get" action="${pageContext.request.contextPath}/goods">
<input type="submit" id="home" value="home" />
</form>
</div>
</body>
</html>
2252 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Sr No. Description
(1) Method of payment is card payment when card number is registered as the account information.
It is considered as cash payment when card number is not registered.
“/session-tutorial-init-web/src/main/webapp/WEB-INF/views/order/finish.jsp”
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Order Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div>
</table>
<table>
<tr>
<th>Name</th>
<th>Price</th>
<th>Quantity</th>
</tr>
<c:forEach items="${order.orderLines}" var="orderLine" varStatus="status">
<tr>
<td><span id="itemName${status.index}">${f:h(orderLine.goods.name)}</span></td
<td><span id="itemPrice${status.index}"><fmt:formatNumber
value="${orderLine.goods.price}" type="CURRENCY"
currencySymbol="¥" maxFractionDigits="0" /></span></td>
<td><span id="itemQuantity${status.index}">${f:h(orderLine.quantity)}</span></
</tr>
</c:forEach>
<tr>
<td>Total</td>
<td><span id="totalPrice"><fmt:formatNumber
value="${f:h(order.totalAmount)}" type="CURRENCY"
currencySymbol="¥" maxFractionDigits="0" /></span></td>
<td></td>
</tr>
</table>
</div>
<div>
<form method="get" action="${pageContext.request.contextPath}/goods">
<input type="submit" id="home" value="home" />
</form>
</div>
</body>
</html>
Checking operation
Products registered in the cart can be ordered with the implementation so far. Transition to the order confirmation
screen takes place by clicking ‘confirm your order’ button on the cart display screen. Order is completed by
clicking ‘order’ button on the order confirmation screen.
Cart object in the session when the order is completed, is deleted with the implementation so far. Therefore,
contents of the cart are cleared when the user returns to the product list screen after the order completion.
“/session-tutorial-init-web/src/main/java/com/example/session/app/config/EnableSynchronizeOnSessionPostProcessor.java”
2254 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
package com.example.session.app.config;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
adapter.setSynchronizeOnSession(true); // (1)
}
return bean;
}
}
(1) Requests within the same session are synchronized by specifying true in the argument of
setSynchronizeOnSession method.
“/session-tutorial-init-web/src/main/resources/META-INF/spring/spring-mvc.xml”
Set the timeout time in web.xml. Set the default value as 30 minutes.
<session-config>
<!-- 30min -->
<session-timeout>30</session-timeout>
</session-config>
Use the Spring Security function for request detection after timeout.
“/session-tutorial-init-web/src/main/resources/META-INF/spring/spring-security.xml”
(1) Mention the destination for transition when request after the timeout is detected in invalid-session-url
attribute of sec:session-management tag.
10.3.6 Conclusion
2256 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Table of Contents
• Introduction
– Topics covered in this tutorial
– Target Audience
– Verification environment
• Overview of application to be created
• Creating environment
– Creating a project
• Creating an application
– Implementing domain layer
* Creating a Domain Object
* Creating AccountRepository
* Creating AccountSharedService
* Creating Authentication Service
* Setting database initialization script
* Package Explorer after creating Domain Layer
– Implementing Application Layer
* Spring Security settings
* Creating login page
* Accessing account information of login user from JSP
* Adding logout button
* Accessing account information of login user from Controller
* Package explorer after creating application layer
• Summary
• Appendix
– Description of configuration file
* spring-security.xml
* spring-mvc.xml
10.4.1 Introduction
Target Audience
Verification environment
• There is a Welcome page and Account information display page which can be viewed only by the logged
in users.
2258 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Creating a project
Create A blank project of TERASOLUNA Server Framework for Java (5.x) using Maven archetype.
Basic knowledge such as how to import to Spring Tool Suite(STS), how to start an application server, etc. is
omitted in this tutorial, since it is already described in Tutorial (Todo Application).
Most of the settings which are required for executing this tutorial are already performed in blank project. It is
not mandatory to understand these settings just for executing the tutorial; however, it is recommended that you
understand the settings which are required to run the application.
For description about settings required to run the application (configuration file), refer to “Description of configu-
ration file”.
2. When user information exists, compare the password stored in the corresponding user information with the
hashed password that has been entered.
If user information is not found or if the passwords do not match, authentication fails.
In domain layer, process to fetch Account object from user name is essential. The process is implemented in the
following order.
2. Creation of AccountRepository
3. Creation of AccountSharedService
Create Account class that stores authentication information (user name and password).
src/main/java/com/example/security/domain/model/Account.java
package com.example.security.domain.model;
import java.io.Serializable;
2260 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
@Override
public String toString() {
return "Account [username=" + username + ", password=" + password
+ ", firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
Creating AccountRepository
src/main/java/com/example/security/domain/repository/account/AccountRepository.java
package com.example.security.domain.repository.account;
import com.example.security.domain.model.Account;
src/main/resources/com/example/security/domain/repository/account/AccountRepository.xml
2262 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
account
WHERE
username = #{username}
</select>
</mapper>
Creating AccountSharedService
Since this process is to be used from Spring Security’s Authentication service, interface name would be
AccountSharedService and class name would be AccountSharedServiceImpl.
Note: This guideline does not recommend calling a Service from another Service.
To have common domain layer process (service), it is recommended to name it as XxxSharedService instead
of XxxService to indicate that it is a service common across various service processes.
The application created in this tutorial does not require common services. However, in general application, it is
assumed to have common services for processing the account information. Therefore, in this tutorial, process to
fetch the account information is implemented as SharedService.
src/main/java/com/example/security/domain/service/account/AccountSharedService.java
package com.example.security.domain.service.account;
import com.example.security.domain.model.Account;
src/main/java/com/example/security/domain/service/account/AccountSharedServiceImpl.java
package com.example.security.domain.service.account;
import javax.inject.Inject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
import com.example.security.domain.model.Account;
import com.example.security.domain.repository.account.AccountRepository;
@Service
public class AccountSharedServiceImpl implements AccountSharedService {
@Inject
AccountRepository accountRepository;
@Transactional(readOnly=true)
@Override
public Account findOne(String username) {
// (1)
Account account = accountRepository.findOne(username);
// (2)
if (account == null) {
throw new ResourceNotFoundException("The given account is not found! username="
+ username);
}
return account;
}
(1) Fetch single Account object that matches with the user name.
(2) If Account that matches with the user name does not exist, throw
ResourceNotFoundException provided by common library.
2264 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Create a class to store authenticated user information which is used in Spring Security.
src/main/java/com/example/security/domain/service/userdetails/SampleUserDetails.java
package com.example.security.domain.service.userdetails;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import com.example.security.domain.model.Account;
(2) Maintain account information of this project in Spring’s authentication user class.
(3) Call constructor of User class. The first argument is user name, the second is password and the third
is authority list.
(4) As a simple implementation, create an authority having only a role named as "ROLE_USER".
(5) Create getter of account information. This enables fetching of Account object of login user.
Create a service to fetch authentication user information which is used in Spring Security.
src/main/java/com/example/security/domain/service/userdetails/SampleUserDetailsService.j
package com.example.security.domain.service.userdetails;
import javax.inject.Inject;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.terasoluna.gfw.common.exception.ResourceNotFoundException;
2266 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
import com.example.security.domain.model.Account;
import com.example.security.domain.service.account.AccountSharedService;
@Service
public class SampleUserDetailsService implements UserDetailsService { // (1)
@Inject
AccountSharedService accountSharedService; // (2)
@Transactional(readOnly=true)
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
Account account = accountSharedService.findOne(username); // (3)
return new SampleUserDetails(account); // (4)
} catch (ResourceNotFoundException e) {
throw new UsernameNotFoundException("user not found", e); // (5)
}
}
(1) Implement
org.springframework.security.core.userdetails.UserDetailsService
interface.
(3) Delegate the process of fetching Account object from username to AccountSharedService.
(4) Create project specific UserDetails object using the fetched Account object, and return as the
return value of method.
In this tutorial, H2 database (in memory database) is used as a database to store account information. As a result,
database initialization is necessary by executing SQL at the time of starting the application server.
Add the settings for executing SQL script that is used to initialize the database.
src/main/resources/META-INF/spring/first-springsecurity-env.xml
2268 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
Create DDL statement for creating a table that stores account information.
src/main/resources/database/H2-schema.sql
src/main/resources/database/H2-dataload.sql
Following are URL patterns to be handled by the application created in this tutorial.
2270 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
URL Description
/login.jsp?error=true URL to display transition page (login page) in case of authentication error
Add following settings apart from the settings provided by blank project.
src/main/resources/META-INF/spring/spring-security.xml
2272 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<sec:http>
<sec:headers>
<sec:cache-control />
<sec:content-type-options />
<sec:hsts />
<sec:frame-options />
<sec:xss-protection />
</sec:headers>
<sec:csrf />
<sec:access-denied-handler ref="accessDeniedHandler"/>
<sec:custom-filter ref="userIdMDCPutFilter" after="ANONYMOUS_FILTER"/>
<sec:session-management />
<!-- (1) -->
<sec:form-login
login-page="/login.jsp"
authentication-failure-url="/login.jsp?error=true" />
<!-- (2) -->
<sec:logout
logout-url="/logout"
logout-success-url="/"
delete-cookies="JSESSIONID" />
<!-- (3) -->
<sec:intercept-url pattern="/login.jsp" access="permitAll" />
<sec:intercept-url pattern="/**" access="isAuthenticated()" />
</sec:http>
<sec:authentication-manager>
<!-- com.example.security.domain.service.userdetails.SampleUserDetailsService
is scanned by component scan with @Service -->
<!-- (4) -->
<sec:authentication-provider
user-service-ref="sampleUserDetailsService">
<!-- (5) -->
<sec:password-encoder ref="passwordEncoder" />
</sec:authentication-provider>
</sec:authentication-manager>
<entry
key="org.springframework.security.web.csrf.MissingCsrfTokenException">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/missingCsrfTokenError.jsp" />
</bean>
</entry>
</map>
</constructor-arg>
<constructor-arg index="1">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp" />
</bean>
</constructor-arg>
</bean>
</beans>
2274 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Note: Default URL provided by Spring Security is changed for the URLs that perform authentication and logout
process.
This is because, a string (spring_security) that implies the usage of Spring Security is included in these
URLs. When default URL is used as it is and if security vulnerability is detected in Spring Security, please be
careful as it becomes easy to receive attacks from a malicious user.
<!DOCTYPE html>
<html>
<head>
<title>Login Page</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h3>Login with Username and Password</h3>
2276 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
</tr>
</table>
</form:form>
</div>
</body>
</html>
Set URL for authentication ("/login") in action attribute of <form:form> tag. This URL is
default for Spring Security.
(3)
Send parameters necessary for authentication (user name and password) using POST method.
Ensure that exception object of authentication error stored in session scope could be fetched from JSP.
src/main/webapp/WEB-INF/views/common/include.jsp
(6)
Note: As per default settings of blank project, session scope cannot be accessed from JSP. This is to ensure that
the session cannot be easily used; however, in case of fetching an exception object of authentication error from
JSP, it is necessary to be accessible from a JSP by session scope.
Try to display the welcome page by entering http://localhost:8080/first-springsecurity/ in browser address bar.
Since the user is not logged in, it is transited to the set value of login-page attribute of
<sec:form-login> tag (http://localhost:8080/first-springsecurity/login.jsp), and the screen below is
displayed.
Access the account information of login user from JSP and display the name.
src/main/webapp/WEB-INF/views/welcome/home.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
2278 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<body>
<div id="wrapper">
<h1>Hello world!</h1>
<p>The time on the server is ${serverTime}.</p>
<!-- (2) -->
<p>Welcome ${f:h(account.firstName)} ${f:h(account.lastName)} !!</p>
<ul>
<li><a href="${pageContext.request.contextPath}/account">view account</a></li>
</ul>
</div>
</body>
</html>
(2)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Hello world!</h1>
<p>The time on the server is ${serverTime}.</p>
<p>Welcome ${f:h(account.firstName)} ${f:h(account.lastName)} !!</p>
<p>
<!-- (1) -->
<form:form action="${pageContext.request.contextPath}/logout">
<button type="submit">Logout</button>
</form:form>
</p>
<ul>
<li><a href="${pageContext.request.contextPath}/account">view account</a></li>
</ul>
</div>
</body>
</html>
Click Logout button to log out from the application (login page is displayed).
2280 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Access account information of login user from Controller and pass it to View.
src/main/java/com/example/security/app/account/AccountController.java
package com.example.security.app.account;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.security.domain.model.Account;
import com.example.security.domain.service.userdetails.SampleUserDetails;
@Controller
@RequestMapping("account")
public class AccountController {
@RequestMapping
public String view(
@AuthenticationPrincipal SampleUserDetails userDetails, // (1)
Model model) {
// (2)
Account account = userDetails.getAccount();
model.addAttribute(account);
return "account/view";
}
}
(2) Fetch Account object which is retained by SampleUserDetails object and store it in Model in
order to pass it to View.
Access the account information passed from Controller to display the same.
src/main/webapp/WEB-INF/views/account/view.jsp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
<link rel="stylesheet"
href="${pageContext.request.contextPath}/resources/app/css/styles.css">
</head>
<body>
<div id="wrapper">
<h1>Account Information</h1>
<table>
<tr>
<th>Username</th>
<td>${f:h(account.username)}</td>
</tr>
<tr>
<th>First name</th>
<td>${f:h(account.firstName)}</td>
</tr>
<tr>
<th>Last name</th>
<td>${f:h(account.lastName)}</td>
</tr>
</table>
</div>
</body>
</html>
2282 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Click ‘view account’ link on welcome page to display “Show account information” page of login user.
10.4.5 Summary
10.4.6 Appendix
Describe configuration file to understand which settings are necessary for using Spring Security.
spring-security.xml
2284 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
value="/WEB-INF/views/common/error/missingCsrfTokenError.jsp" />
</bean>
</entry>
</map>
</constructor-arg>
<constructor-arg index="1">
<bean
class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
<property name="errorPage"
value="/WEB-INF/views/common/error/accessDeniedError.jsp" />
</bean>
</constructor-arg>
</bean>
</beans>
2286 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
Control login related operation which use form authentication, by using <sec:form-login> tag.
For usage method, refer to “Form authentication”.
(2)
Enable servlet filter to store authentication user name of Spring Security in logger MDC. Once this
setting is enabled, authentication user name is output in log thereby enhancing the traceability.
(5)
spring-mvc.xml
<context:property-placeholder
location="classpath*:/META-INF/spring/*.properties" />
<mvc:annotation-driven>
<mvc:argument-resolvers>
<bean
class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
<!-- (1) -->
<bean
class="org.springframework.security.web.method.annotation.AuthenticationPrincipalA
</mvc:argument-resolvers>
</mvc:annotation-driven>
<mvc:default-servlet-handler />
<mvc:resources mapping="/resources/**"
location="/resources/,classpath:META-INF/resources/"
cache-period="#{60 * 60}" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.logging.TraceLoggingInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
2288 10 Tutorials
TERASOLUNA Server Framework for Java (5.x) Development Guideline Documentation,
Release 5.2.0.RELEASE
<bean class="org.terasoluna.gfw.web.codelist.CodeListInterceptor">
<property name="codeListIdPattern" value="CL_.+" />
</bean>
</mvc:interceptor>
<!-- REMOVE THIS LINE IF YOU USE JPA
<mvc:interceptor>
<mvc:mapping path="/**" />
<mvc:exclude-mapping path="/resources/**" />
<mvc:exclude-mapping path="/**/*.html" />
<bean
class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
</mvc:interceptor>
REMOVE THIS LINE IF YOU USE JPA -->
</mvc:interceptors>
<bean id="requestDataValueProcessor"
class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor">
<constructor-arg>
<util:list>
<!-- (2) -->
<bean class="org.springframework.security.web.servlet.support.csrf.CsrfRequestData
<bean
class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataVal
</util:list>
</constructor-arg>
</bean>
</beans>
2290 10 Tutorials
2291
11
Appendix(Know How)
Sonatype NEXUS is the package repository manager software. OSS version as well as commercial version of
NEXUS is available. However its OSS version also has adequate functionalities.
This chapter explains the role and configuration method of OSS version of NEXUS.
When there is only one developer, a central repository on internet and a local repository on developer’s machine
can be developed using Maven or ant+ivy.
However, when a Java application is to be divided into multiple sub-projects and development is to be carried
out in a team, library dependency resolution becomes complex; hence this dependency resolution needs to be
automated. For this, availability of package repository server is essential.
The following package repositories are required in Java application development project.
• Proxy repository creates proxy to access external repository server including the central repository
• 3rd party repository for distributing the artifacts provided by others within the organization that are not
available in the repository on internet
• Private repository for storing the artifacts developed within the project
• Group repository for consolidating access to artifacts of different multiple repositories into a single repos-
itory URL
The machine on which NEXUS is to be installed should satisfy the following conditions.
3. Access http://[IP or FQDN]:8081/nexus/ and check whether welcome screen of NEXUS is displayed.
Some repositories are provided by default. Except for a few cases, they are used as is for development. Repository
list is displayed on clicking Repositories on the menu on the left side of screen.
• Central = This repository plays a role of proxy to the central repository on internet
(http://repo1.maven.org/maven2/).
• 3rd party = This repository stores third-party libraries required in development but not available in the
repositories on internet.
• Releases = This repository stores the work products of release version of the applications developed inter-
nally.
• Snapshots = This repository stores the work products of SNAPSHOT version of the applications developed
internally.
• Public Repositories This group repository is used for enabling access to the above repositories through a
single URL.
When an application is to be developed using TERASOLUNA Server Framework for Java (5.x), TERASOLUNA
Server Framework for Java (5.x) repository needs to be added in addition to the above repositories.
Todo
11.1.4 settings.xml
In order to use the created NEXUS using Maven command, settings.xml file needs be created in the home directory
of local development environment of the user.
• Windows: C:/Users/[OSaccount]/.m2/settings.xml
• Unix: $HOME/.m2/settings.xml
<mirrors>
<mirror>
<id>myteam-nexus</id>
<mirrorOf>*</mirrorOf>
<!-- CHANGE HERE by your team own nexus server -->
<url>http:// IP or FQDN /nexus/content/groups/public </url>
</mirror>
</mirrors>
<activeProfiles>
<activeProfile>myteam-nexus</activeProfile>
</activeProfiles>
<profiles>
<profile>
<id>myteam-nexus</id>
<repositories>
<repository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>http://central</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>
Note: Additionally, also refer to: Configuring Maven to Use a Single Repository Group / Documentation
Sonatype.com
Use mvn deploy command to upload jar/war file to the package repository (NEXUS) as an artifact.
A state in which anyone can deploy the application in package repository should be avoided as it causes confusion.
Therefore, it is desirable that mvn deployment for package repository is possible only by Jenkins.
In addition to the contents mentioned earlier, add the following to .m2/settings.xml under the home directory of
the user executing Jenkins of Jenkins server.
<servers>
<server>
<id>releases</id>
<username>deployment</username>
<password>deployment123</password>
</server>
<server>
<id>snapshots</id>
<username>deployment</username>
<password>deployment123</password>
</server>
</servers>
‘deployment’ is the account (set in NEXUS by default) having deployment permission and its password is deploy-
ment123. It is recommended to change the password in advance on NEXUS GUI screen.
Note: To avoid saving the password in plain text in settings.xml, it is advisable to use password encryption
function of Maven. Refer to Maven - Password Encryption for details.
Carry out the mvn deployment procedure in the build job of Jenkins as follows:
Todo
11.1.6 pom.xml
In case of the project managed in Maven, package repository in which artifact is stored, should be specified using
<distributionManagement> tag of pom.xml.
<distributionManagement>
<repository>
<id>releases</id>
<!-- CHANGE HERE by your team nexus server -->
<url>http://192.168.0.1:8081/nexus/content/repositories/releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<!-- CHANGE HERE by your team nexus server -->
<url>http://192.168.0.1:8081/nexus/content/repositories/snapshots/</url>
</snapshotRepository>
</distributionManagement>
The mvn deploy command uploads the artifact with HTTP PUT for the URL specified using <distributionMan-
agement> tag.
In the 3rd party repository, store the artifact which is not disclosed in external remote repository.
Typical example is JDBC driver (ojdbc*.jar) of oracle. Oracle should be used as RDBMS; however, central
repository is not stored in the public repository on internet. Therefore, it should be stored in the package repository
in the organization.
5. At the end, click Upload Artifact(s) button to save the jar file in repository.
Note: Uploading artifacts using NEXUS GUI screen is a manual task which can easily lead to operational errors.
Hence it is not recommended. The method explained here should be used only for simple configurable libraries
having 1 or more 3rd party files such as ojdbc6.jar. mvn deploy command should be used for other cases.
use artifact
In order to add ojdbc6 of 3rd party repository to project dependency management, just add dependency tag to
pom.xml of the corresponding project.
If the target artifact is selected from Browse Storage tab, sample of dependency tag is displayed on the right side
of the screen. It just needs to be copied and pasted in pom.xml.
11.2.1 Lombok
Lombok is a library used for reducing the boilerplate code from Java source code.
Boilerplate code is a typical source code that cannot be omitted by language specification. Basically Boilerplate
code does not have a specific logic hence it becomes redundant code in implementation.
• equals/hashCode methods
• toString methods
• Constructors
In Lombok, boilerplate code gets generated at the time of compilation thereby providing a mechanism to remove
redundant code from the source code developed by the developer.
Tip: For removing the Boilerplate code, the language specification for closing the resources (input and output
stream, etc.) are improved by newly added [try-with-resources] statement in Java SE7.
Java language itself is improving in every version-up for removing the redundant code. Lambda support in Java
SE8 is also called as typical language specification improvement.
package com.example.domain.model;
@lombok.Data
public class User {
In Lombok, required methods for JavaBean gets created by only assigning @lombok.Data annotation at class
level.
By only assigning @Data annotation of Lombok, it is possible to obtain the same effect as classes generated by
(the source code output using the auto-generation function of Eclipse) which has about 60 lines of code given
below instead of 10 lines of code.
package com.example.domain.model;
public User() {
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((password == null) ? 0 : password.hashCode());
result = prime * result + ((userId == null) ? 0 : userId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (userId == null) {
if (other.userId != null)
return false;
} else if (!userId.equals(other.userId))
return false;
return true;
}
@Override
public String toString() {
return "User [userId=" + userId + ", password=" + password + "]";
}
In order to use a class that is offered by Lombok, add Lombok as dependency library.
Sr.No Description
Add Lombok dependent library in the Lombok targeted project’s pom.xml .
(1)
Since Lombok library is not required at the time of application execution, appropriate scope is
provided.
(2)
Note: In the above configuration example, it is prerequisite that the version of dependent library is to be managed
by the parent project. Therefore, <version> element is not specified.
IDE Integration
If you want to use Lombok on IDE, it is necessary to install the Lombok to IDE in order to work with compile
(build) function provided by the IDE.
In this guideline, introduced how to install Lombok to Spring Tool Suite (Later referred as the “STS”). However
installation methods are different depending on IDE henceforth refer this page in case you want to use IDE besides
STS.
Download Lombok
Lombok Installation
Launch the installer by running (double-click) the downloaded Lombok jar file.
After selecting the targeted STS, follow installation process by pressing the “Install / Update” button. The installer
will automatically detect the location of supported IDE However if cannot auto detected, it is necessary to specify
Once Lombok installation completes, it is possible to start development using Lombok on STS after booting (Or
re-booting) STS.
If Lombok is used first time, it is recommendation to watch Lombok [Demo Video]. The length of Demo Video is
less than 4 minutes and described the most basic usage.
Lombok Annotations
For Detailed usage of each annotation as well as annotation that have not been explained in this guideline,
please refer,
• Lombok features
Creation of JavaBean
• Form class
• Entity class
• DTO class
package com.example.domain.model;
import lombok.Data;
@Data // (1)
public class User {
Sr.No Description
By assigning a @Data annotation at class level,
• getter/setter method
(1)
• equals/ hashCode method
• toString method
• default constructor
are created.
• Field that holds sensitive information such as personal information and password
etc are required to exclude from the scope of string conversion. If these fields are not excluded from the string
conversion,
• There is a possibility of leakage the personal information due to use of converted string
Warning: If @Data or @ToString annotation is used at the Entity class of JPA, it is necessary to keep in
mind that it tends to the circular reference.
How to exclude a specific field from string conversions are indicated below.
package com.example.domain.model;
import lombok.Data;
import lombok.ToString;
@Data
@ToString(exclude = "password") // (1)
public class User {
Sr.No Description
Specify the @ToString annotation to the class level and list the name of fields that you want to
exclude into exclude attribute.
(1)
If you call toString method of the class that is generated from the source code of above example,
• User(userId=U00001)
is converted to the string.
If equals method and hashCode method generated using Lombok annotation, field that holds an object of
cross reference relationship needs to be removed.
If methods are generated without excluding these fields, the StackOverflowError and
OutOfMemoryError occurs due to circular reference henceforth it is necessary to take an attention.
Warning: If @Data annotation, @Value annotation, @EqualsAndHash annotation is used at the Entity
class of JPA, it is necessary to keep in mind that it tends to the circular reference.
package com.example.domain.model;
import java.util.List;
import lombok.Data;
@Data
public class Order {
package com.example.domain.model;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Data
@ToString(exclude = "order")
@EqualsAndHashCode(exclude = "order") // (1)
public class OrderLine {
Sr.No Description
Specify the @EqualsAndHashCode annotation to the class level and list the name of fields that you
want to exclude into exclude attribute.
(1)
Tip: Instead of specifying the field to be excluded, it is also possible to specify to use only specific fields.
@Data
@ToString(exclude = "order")
@EqualsAndHashCode(of = "itemCode") // (2)
public class OrderLine {
Sr.No Description
In case of using only specific fields, specify the name of fields that you want to include into of
attribute of @EqualsAndHashCode annotation.
(2)
In the above example, equals method and hashCode method get generated by referring only
itemCode field.
If you want to create an instance of JavaBean from the implementation code of application, it is more convenient
that having constructor where initial value of the field can be passed as an argument, to eliminate the redundant
code.
If you create an instance using the default constructor, the code would be like below.
How to generate a constructor that specifies the initial values of the field are indicated below.
package com.example.domain.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor // (1)
@NoArgsConstructor // (2)
@ToString(exclude = "password")
public class User {
Sr.No Description
Specify the @AllArgsConstructor annotation to the class level, to generate a constructor that
takes the initial values of all fields as an argument.
(1)
Specify the @NoArgsConstructor annotation to the class level, to generate a default constructor.
It is necessary to generate a default constructor if going to be used as JavaBean.
(2)
Create an instance of JavaBean by calling the constructor that having initial values of the field.
If default constructor is used, instance can be generated in one step instead of 3 steps.
(3)
Tip: If you want to create above User class as Immutable class instead of JavaBean, it is preferable to use
@lombok.Value annotation. Please refer Lombok reference for @Value annotation.
If it is necessary to generate a logger instance for output a debug log and application log, it is preferable to use
annotations for creating a logger instance.
If you want to create a logger instance without using Lombok annotations, below could be code.
package com.example.domain.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class AuthenticationService {
package com.example.domain.service;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Slf4j // (1)
@Service
public class AuthenticationService {
Sr.No Description
Generate SLF4J logger instance by specifying @Slf4j annotation at the class level.
In this guideline, it is prerequisite to output a log using org.slf4j.Logger of SLF4J.
(1)
By default, FQCN class that granted the annotation (In above example
com.example.domain.service.LoginService) is used as the logger name and, log-
ger instance corresponding to the logger name is set to field called log.
Output the log by calling the method of SLF4J logger instance that has been generated by Lombok.
In above example,
(2)
• 11:29:45.838 [main] INFO c.e.d.service.AuthenticationService -
U00001 had tried login.
will be output.
Tip: If you want to change the logger name that is used by default, specify optional logger name in the topic
attribute of @Slf4j annotation.
This guideline was prepared after referring to the following books. Refer to them as per the requirements.
1. Fill (1)-(4) such that the Bean dependency relation is as follows. Skip import statement.
@Controller
public class XxxController {
(1)
protected (2) yyyService;
// omitted
}
@Service
@Transactional
public class YyyServiceImpl implements YyyService {
(1)
protected (4) zzzRepository;
// omitted
}
5. Fill (1)-(3) in the following Scope related description. However, either of “singleton” or “prototype” can
be entered in (1) and (2), but same value cannot be entered for both. Skip the import statement.
@Component
(3)
public class XxxComponent {
// omitted
}
Scope of bean with @Component is (1) by default. When changing scope to (2), it is better to add (3)
(refer to above source code).
7. Fill (1)-(3) of the following Bean definition such that contents in com.example.domain package be-
comes target of component scan.
xmlns:context=”http://www.springframework.org/schema/context”
8. Fill (1)-(2) in the following Properties file related description. Skip import statement.
It is possible to read the Bean definition file in ${key} format by removing the set-
tings in properties file, if properties file path is set in the locations attribute of
<context:property-placeholder> element. Specify as shown in (1) to read any proper-
ties file under META-INF/spring directory under the class path. Moreover, @(2) annotation should be
added as shown in the following codes where the read properties value can also be injected in Bean.
emails.min.count=1
emails.max.count=4
@Service
@Transactional
public class XxxServiceImpl implements XxxService {
@xxx("${emails.min.count}") // (2)
protected int emailsMinCount;
@xxx("${emails.max.count}") // (2)
protected int emailsMaxCount;
// omitted
}
xmlns:context=”http://www.springframework.org/schema/context”
9. Fill (1)-(5) in the following description for AOP Advice of Spring. The contents of (1)-(5) are all different.
Note: Advice (1) should be used when interrupting a process before calling a specific method, Advice
(2) should be used when interrupting a process after calling a specific method. Advice (3) should be
used when interrupting a process before and after calling a specific method. Advice (4) should be used
only when the process is ended normally and Advice (5) should be used when there is an exception.
10. Insert (*) of following Bean definition for performing transaction management using @Transactional
annotation.
<tx:(*) />
xmlns:tx=”http://www.springframework.org/schema/tx”