Tutorial (Todo Application)
********************************************************************************
.. only:: html
.. contents:: Index
:depth: 3
:local:
Introduction
================================================================================
Points to study in this tutorial
--------------------------------------------------------------------------------
* Basic application development and configuration of Eclipse project using TERASOLUNA Global Framework
* Development in accordance with application layering of this TERASOLUNA Global Framework
Target readers
--------------------------------------------------------------------------------
* Basic knowledge of DI and AOP of Spring is required
* Web application development using Servlet/JSP
* SQL knowledge
Verification environment
--------------------------------------------------------------------------------
In this tutorial, operations are verified in the following environment.
.. tabularcolumns:: |p{0.15\linewidth}|p{0.85\linewidth}|
.. list-table::
:header-rows: 1
:widths: 15 85
* - Type
- Name
* - OS
- Windows7 64bit
* - JVM
- Java 1.6
* - IDE
- Spring Tool Suite Version: 3.2.0.RELEASE, Build Id: 201303060821 (henceforth referred to STS) Build Maven 3.0.4 (STS attached)
* - Application Server
- VMWare vFabric tc Server Developer Edition v2.8 (STS attached)
* - Web Browser
- Google Chrome 27.0.1453.94 m
Description of application to be created
================================================================================
Overview of application
--------------------------------------------------------------------------------
Application to manage TODO list is to be developed. TODO list display, TODO registration, TODO completion and TODO deletion can be performed.
.. figure:: ./images/image001.png
:width: 60%
.. _app-requirement:
Business requirements of application
--------------------------------------------------------------------------------
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 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.
|
Screen transition of application
--------------------------------------------------------------------------------
.. figure:: ./images/image002.png
:width: 60%
.. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.15\linewidth}|p{0.15\linewidth}|p{0.40\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 20 15 15 40
* - Sr.No.
- Process name
- HTTP method
- URL
- Description
* - 1
- Show all TODO
- GET
- /todo/list
-
* - 2
- Create TODO
- POST
- /todo/create
- Redirect to 1 after creation is completed
* - 3
- Finish TODO
- POST
- /todo/finish
- Redirect to 1 after creation is completed
* - 4
- Delete TODO
- POST
- /todo/delete
- Redirect to 1 after creation is completed
Show all TODO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Display all records of TODO
* Provide ``Finish`` and ``Delete`` buttons for incomplete TODO
* Strike-through the completed records of TODO
* Only record name of TODO
Create TODO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Save TODO sent from the form
* Record name of TODO should be between 1 - 30 characters
* When :ref:`app-requirement` B01 is not fulfilled, business exception with error code E001 is thrown
Finish TODO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* For the TODOs corresponding to todoId which is received from the form object, change the status to ``completed``.
* When :ref:`app-requirement` B02 is not fulfilled, business exception with error code E002 is thrown
* When the corresponding TODO does not exist, business exception with error code E404 is thrown
Delete TODO
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Delete TODO corresponding to todoId sent from the form
* When the corresponding TODO does not exist, business exception with error code E404 is thrown
Error message list
--------------------------------------------------------------------------------
.. tabularcolumns:: |p{0.15\linewidth}|p{0.45\linewidth}|p{0.40\linewidth}|
.. list-table::
:header-rows: 1
:widths: 15 45 40
* - Error code
- Message
- Parameter to be replaced
* - E001
- [E001] The count of un-finished Todo must not be over {0}.
- {0}… max unfinished count
* - E002
- [E002] The requested Todo is already finished. (id={0})
- {0}… todoId
* - E404
- [E404] The requested Todo is not found. (id={0})
- {0}… todoId
Environment creation
================================================================================
Project creation
--------------------------------------------------------------------------------
Select ``File`` -> ``Other`` -> ``Maven`` -> ``Maven Project`` and proceed to ``Next``
.. figure:: ./images/image004.jpg
:width: 60%
Insert check-mark to ``Create a simple project`` and proceed to ``Next``
.. figure:: ./images/image006.jpg
:width: 60%
.. tabularcolumns:: |p{0.25\linewidth}|p{0.75\linewidth}|
.. list-table::
:widths: 25 75
:stub-columns: 1
* - Group Id:
- org.terasoluna.tutorial
* - Artifact Id:
- todo
* - Packaging:
- war
``Finish``
.. figure:: ./images/image008.jpg
:width: 60%
Project as shown below is created.
.. figure:: ./images/image009.png
:width: 40%
|
.. note::
For better visibility, Package Presentation must be changed to Hierarchical.
.. figure:: ./images/presentation-hierarchical.png
:width: 80%
Maven settings
--------------------------------------------------------------------------------
Change pom.xml as follows.
If basic knowledge of Maven is not there, then just copy the pom.xml and skip the below explanation.
.. code-block:: xml
:emphasize-lines: 9-83
4.0.0org.terasoluna.tutorialtodo0.0.1-SNAPSHOTwarorg.terasoluna.gfwterasoluna-gfw-parent1.0.0.RELEASEtruefalseterasoluna-gfw-releaseshttp://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-releases/falsetrueterasoluna-gfw-snapshotshttp://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-snapshots/truefalseterasoluna-gfw-3rdpartyhttp://repo.terasoluna.org/nexus/content/repositories/terasoluna-gfw-3rdparty/org.terasoluna.gfwterasoluna-gfw-weborg.terasoluna.gfwterasoluna-gfw-security-weborg.terasoluna.gfwterasoluna-gfw-recommended-dependenciespomorg.apache.tomcattomcat-servlet-api7.0.40providedorg.apache.tomcattomcat-jsp-api7.0.40provided
Right click on the project name in "Package Explorer" and select [Maven] -> [Update Project]
.. figure:: ./images/update-project.png
:width: 60%
Press "OK" button.
Confirm that the version of "JRE System Library" is "[JavaSE-1.6]".
.. figure:: ./images/check-jre.jpg
:width: 30%
|
.. note::
In order to update the version of JDK to 7, set ``1.7`` in ```` of pom.xml.
After that, execute "Update Project"
.. code-block:: xml
:emphasize-lines: 4-6
1.7
If you are familiar with the Maven, and to make sure the following discussion.
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Specify parent pom file of TERASOLUNA Global Framework.
| In this way, even without specifying the version, the library defined in terasoluna-parent can be added to dependency.
* - | (2)
- | Specify URL of Maven repository for using TERASOLUNA Global Framework.
* - | (3)
- | Add common library of TERASOLUNA Global Framework to dependency.
* - | (4)
- | Add the library group recommended by TERASOLUNA Global Framework.
| Since terasoluna-gfw-recommended-dependencies is just pom file, ``pom`` should be mentioned.
* - | (5)
- | Add the libraries which are recommended by TERASOLUNA Global Framework
| terasoluna-gfw-recommended-dependencies is just a pom file; hence ``pom`` must be specified.
* - | (6)
- | Add Servlet/JSP API to dependency. Compatiblity with Servlet3 is necessary.
| These API have scope=provided (provided by the original AP server), they are not included in war, but it should be added explicitly to dependency for compiling on eclipse.
| (Further, though dependency name is tomcat-xxx, but the package of embedded class is javax.servlet, there is no dependency on tomcat)
.. note:: When proxy server is used to access the internet,
perform the following settings in /.m2/settings.xml.
(In case of Windows7 C:\\Users\\\\.m2\settings.xml)
.. code-block:: xml
true[Proxy Server Protocol (http)][Proxy Server Port][Proxy Server Host][Username][Password]
Project configuration
--------------------------------------------------------------------------------
Below is the structure of the project to be created.
.. code-block:: console
src
└main
├java
│ └todo
│ ├ app ... Application layer
│ │ └todo ... Classes related todo management business process
│ └domain ... Domain layer
│ ├model ... Domain Object
│ ├repository ... Repository
│ │ └todo ... Repository related to Todo
│ └service ... Services
│ └todo ... Service related to Todo
├resources
│ └META-INF
│ └spring ... configuration files related to Spring
└wepapp
└WEB-INF
└views ... jsp
Since above will be created in order, there is no need to provide prepare the above structure beforehand.
|
.. note::
It had been recommended to use a multi-project structure in :ref:`"Project Structure" section of previous chapter ` .
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.
|
Creation of configuration file
--------------------------------------------------------------------------------
web.xml settings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create ``src/main/webapp/WEB-INF/web.xml`` and define servlet and filters.
New WEB-INF folder should be created by ``New`` -> ``Folder``.
.. figure:: ./images/image010.jpg
:width: 40%
Create web.xml by ``New`` -> ``File``,
.. figure:: ./images/image011.jpg
:width: 40%
and describe the contents as follows.
.. code-block:: xml
org.springframework.web.context.ContextLoaderListenercontextConfigLocation
classpath*:META-INF/spring/applicationContext.xml
CharacterEncodingFilterorg.springframework.web.filter.CharacterEncodingFilterencodingUTF-8forceEncodingtrueCharacterEncodingFilter/*appServletorg.springframework.web.servlet.DispatcherServletcontextConfigLocationclasspath*:META-INF/spring/spring-mvc.xml1appServlet/*.jspfalseUTF-8false/WEB-INF/views/common/include.jsp
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Declaration for using Servlet3.0.
* - | (2)
- | Define ``ContextLoaderListener``. ``ApplicationContext`` created by this listener is the root context.
| Path of Bean definition file is ``META-INF/spring/applicationContext.xml`` just under the classpath.
* - | (3)
- | Define ``CharacterEncodingFilter``. This is done for changing the character encoding of request and response to UTF-8.
* - | (4)
- | Define ``DispatcherServlet`` that is the entry point of Spring MVC.
| Path of Bean definition file to be used in Spring MVC is ``META-INF/spring/spring-mvc.xml`` just under the classpath.
| ``ApplicationContext`` created here is the child of ``ApplicationContext`` created in step (2).
* - | (5)
- | Define common JSP to be included. Include ``/WEB-INF/views/common/include.jsp`` for any JSP(\*.JSP)
.. figure:: ./images/image013.png
:width: 40%
|
Settings of common JSP
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Describe the contents to be included in each common JSP in src/main/webapp/WEB-INF/views/common/include.jsp. Also define taglib in common area.
Create views/common folder and include.jsp file and describe as follows.
.. code-block:: jsp
<%@ page session="false"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>
<%@ taglib uri="http://terasoluna.org/functions" prefix="f"%>
<%@ taglib uri="http://terasoluna.org/tags" prefix="t"%>
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Define standard tag library.
* - | (2)
- | Define tag library for Spring MVC.
* - | (3)
- | Define tag library for Spring Security.(However, it is not used in this tutorial)
* - | (4)
- | Define EL function and tag library provided in common library.
.. figure:: ./images/image014.png
:width: 40%
|
Settings of Bean definition file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create 4 types of Bean definition files in the following order.
* applicationContext.xml
* todo-domain.xml
* todo-infra.xml
* spring-mvc.xml
applicationContext.xml
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Carry out settings related to entire Todo application in src/main/resources/META-INF/spring/applicationContext.xml.
Create META-INF/spring folder and create applicationContext.xml using ``New`` -> ``Spring Bean Configuration File``.
.. figure:: ./images/image016.jpg
:width: 40%
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Import Bean definition file related to domain layer.
* - | (2)
- | Read the settings of property file.
| Read any property file under ``src/main/resources/META-INF/spring``.
| Using this setting, it is possible to insert property file value in ${propertyName} format in Bean definition file and to inject using @Value("${propertyName}") in Java class.
* - | (3)
- | Define Mapper of library Dozer for Bean conversion.
| (Not used in this tutorial, but while defining XML file for mapping, it should be created in ``src/main/resources/META-INF/dozer/xxx-mapping.xml`` format.
| For the mapping file, refer to `Dozer manual `_ .)
.. figure:: ./images/image018.png
:width: 40%
|
.. note::
While entering the above contents manually without copying them, open ``namespace`` tab and insert check-mark in ``beans`` and ``context`` in ``Configure Namespace``.
It is recommended to select xsd file without version in ``Namespace Versions``.
.. figure:: ./images/image021.jpg
:width: 60%
:align: center
Thus, at the time of editing XML, it is possible to supplement the input using Ctrl+Space.
.. figure:: ./images/image023.png
:width: 60%
:align: center
Moreover, by not specifying the version, latest xsd included in the jar is used.
|
todo-domain.xml
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Carry out settings related to domain layer in ``src/main/resources/META-INF/spring/todo-domain.xml``.
Create todo-domain.xml using ``New`` -> ``Spring Bean Configuration File`` under ``META-INF/spring``.
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Import Bean definition file related to infrastructure layer (explained later).
* - | (2)
- | Components under todo.domain package are target of component scan.
| Thus, it is possible to make DI target by attaching annotations like ``@Repository`` , ``@Service`` , ``@Controller``, ``@Component`` to the class under todo.domain package.
.. figure:: ./images/image024.png
:width: 40%
|
todo-infra.xml
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Define Beans related to infrastructure layer in ``src/main/resources/META-INF/spring/todo-infra.xml``.
Here, DB setting are carried out, but DB is not used in this section, hence the definition may be blank as follows. Bean will be defined in next section.
Create todo-infra.xml using ``New`` -> ``Spring Bean Configuration File`` under ``META-INF/spring``.
.. code-block:: xml
.. figure:: ./images/image025.png
:width: 40%
|
.. note:: All the contents of todo-domain.xml, todo-infra.xml may likely be described in applicationContext.xml, however
it is recommended to split the file for each layer. It will be easy to understand the definitions at various locations and to improve maintainability.
There is no effect on a small application like the current tutorial, but larger the scope more the effect.
spring-mvc.xml
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Define Spring MVC related definitions in ``src/main/resources/META-INF/spring/spring-mvc.xml``.
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Carry out annotation based default settings of Spring MVC.
* - | (2)
- | Components under todo.app package that holds classes of application layer are made target of component-scan.
* - | (3)
- | 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 ``/resources/css/styles.css``, ``WEB-INF/resources/css/styles.css`` is searched.
| If not found, ``resources/css/style.css`` is searched in classpath (src/main/resources and jar). If not found again, 404 error is returned.
| Cache period (3600 seconds = 60 minutes) of static resources is set in ``cache-period`` attribute.
| Further, static resources are not used in this tutorial.
| ``cache-period="3600"`` is also correct, however, in order to demostrate that it is 60 minutes, it is better to write as ``cache-period="#{60 * 60}"`` which uses `SpEL `_ .
* - | (4)
- | Set interceptor that outputs trace log of controller processing. Set so that it excludes the path under ``/resources`` from mapping.
* - | (5)
- | Carry out the settings of ViewResolver. Using these settings, for example, when view name ``hello`` is returned from controller, ``/WEB-INF/views/hello.jsp`` is executed.
.. figure:: ./images/image026.png
:width: 40%
|
.. note:: While entering the above contents manually without copying them, in addition to the operations described in todo-domain.xml, check mark should be put also to "mvc" and "util".
.. figure:: ./images/image028.png
:width: 60%
:align: center
|
logback.xml settings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Carry out log output settings using logback in ``src/main/resources/logback.xml``.
Create logback.xml by ``New`` -> ``File`` just under ``src/main/resources/``.
.. code-block:: xml
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Set appender that outputs the log in standard output.
* - | (2)
- | Set so that log of level 'debug' and above is output under todo package.
* - | (3)
- | Change the log level of common library to info.
* - | (4)
- | Set log level to 'trace' for ``TraceLoggingInterceptor`` which is defined in spring-mvc.xml.
* - | (5)
- | Set so that log of level 'warn' and above is output for Spring Framework.
* - | (6)
- | For log of Spring Framework, set the log level to 'info' and above for ``org.springframework.web.servlet`` so that logs valueable to development activity gets output.
* - | (7)
- | Set so that log level of 'warn' and above is output by default.
.. figure:: ./images/image029.png
:width: 40%
|
Operation verification
--------------------------------------------------------------------------------
Before starting development of Todo application, create SpringMVC HelloWorld application and verify the operation.
.. tabularcolumns:: |p{0.25\linewidth}|p{0.75\linewidth}|
.. list-table::
:widths: 25 75
:stub-columns: 1
* - Package:
- todo.app.hello
* - Name:
- HelloController
Create todo.app.hello.HelloController using ``New`` -> ``Class``.
.. figure:: ./images/image030.jpg
:width: 40%
Edit HelloController as shown below.
.. code-block:: java
package todo.app.hello;
import java.util.Date;
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;
// (1)
@Controller
public class HelloController {
// (2)
private static final Logger logger = LoggerFactory
.getLogger(HelloController.class);
// (3)
@RequestMapping("/")
public String hello(Model model) {
Date now = new Date();
// (4)
logger.debug("hello {}", now);
// (5)
model.addAttribute("now", now);
// (6)
return "hello";
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | In order to make the Controller as component-scan target, attach ``@Controller`` annotation to class level.
* - | (2)
- | Generate logger. Since logback implements logger and API is SLF4J, ``org.slf4j.Logger`` should be used.
* - | (3)
- | Set mapping of methods for accessing ``/`` (root) using ``@RequestMapping``.
* - | (4)
- | Output debug log. ``{}`` is the placeholder.
* - | (5)
- | For passing date to the screen, add Date object with name ``now`` to Model.
* - | (6)
- | Return hello as view name. Using ViewResolver settings, WEB-INF/views/hello.jsp is output.
Next, create view(jsp). Create src/main/webapp/WEB-INF/views/hello.jsp as follows.
.. code-block:: jsp
Hello World!
Hello World!
Today is
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Display ``now`` passed from Controller. Here, ```` tag is used for date formating.
Right click package project name ``todo`` and click ``Run As`` -> ``Run on Server``
.. figure:: ./images/image031.jpg
:width: 40%
Select AP server (here, VMWare vFabric tc Server Developer Edition v2.8) to be executed
Click ``Next``
.. figure:: ./images/image032.jpg
:width: 40%
Verify that todo is included in ``Configured``, click ``Finish`` to start the server.
.. figure:: ./images/image033.jpg
:width: 40%
When started, log shown as below will be output. For ``/`` path, it is understood that hello method of ``todo.app.hello.HelloController`` is mapped.
.. code-block:: guess
:emphasize-lines: 3
2013-06-14 14:26:54 [localhost-startStop-1] [WARN ] [org.dozer.config.GlobalSettings ] - Dozer configuration file not found: dozer.properties. Using defaults for all Dozer global properties.
2013-06-14 14:26:54 [localhost-startStop-1] [INFO ] [o.springframework.web.servlet.DispatcherServlet ] - FrameworkServlet 'appServlet': initialization started
2013-06-14 14:26:54 [localhost-startStop-1] [INFO ] [o.s.w.s.m.m.a.RequestMappingHandlerMapping ] - Mapped "{[/],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String todo.app.hello.HelloController.hello(org.springframework.ui.Model)
2013-06-14 14:26:55 [localhost-startStop-1] [INFO ] [o.s.web.servlet.handler.SimpleUrlHandlerMapping ] - Mapped URL path [/resources/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
2013-06-14 14:26:55 [localhost-startStop-1] [INFO ] [o.springframework.web.servlet.DispatcherServlet ] - FrameworkServlet 'appServlet': initialization completed in 986 ms
|
.. note:: WARN log of the first row may be ignored. In order to prevent, a blank dozer.properties should be created in src/main/resources.
If http://localhost:8080/todo is accessed in browser,
following is displayed.
.. figure:: ./images/image034.png
:width: 40%
If you see console, you will understand that TRACE log using ``TraceLoggingInterceptor`` and debug log implemented by Controller is output.
.. code-block:: guess
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [START CONTROLLER] HelloController.hello(Model)
2013-06-14 15:40:59 [tomcat-http--3] [DEBUG] [todo.app.hello.HelloController ] - hello Fri Jun 14 15:40:59 JST 2013
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [END CONTROLLER ] HelloController.hello(Model)-> view=hello, model={now=Fri Jun 14 15:40:59 JST 2013}
2013-06-14 15:40:59 [tomcat-http--3] [TRACE] [o.t.gfw.web.logging.TraceLoggingInterceptor ] - [HANDLING TIME ] HelloController.hello(Model)-> 15,043,704 ns
|
.. note:: ``TraceLoggingInterceptor`` outputs start and end of Controller in log. While ending, View and Model information and processing time are output.
After verification of log, one can delete HelloController and hello.jsp.
|
Creation of Todo application
================================================================================
| Create Todo application. Order in which it must be created is as follows
* Domain layer (+ Infrastructure layer)
* Domain Object creation
* Repository creation
* Service creation
* Application layer
* Controller creation
* Form creation
* View creation
Further, do not use DB for saving Todo in this section. Creation of Repository in which DB is used, is carried out in \ :ref:`tutorial-todo_infra`\ .
|
Creation of Domain layer
--------------------------------------------------------------------------------
Creation of Domain Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Following properties are required in domain object.
#. ID
#. Title
#. Completion flag
#. Created on
Create the following Domain objects.
FQCN should be ``todo.domain.model.Todo``. Implement as JavaBean.
.. tabularcolumns:: |p{0.25\linewidth}|p{0.75\linewidth}|
.. list-table::
:widths: 25 75
:stub-columns: 1
* - Package:
- todo.domain.model
* - Name:
- Todo
* - Interfaces:
- java.io.Serializable
.. figure:: ./images/image057.png
:width: 40%
.. code-block:: java
package todo.domain.model;
import java.io.Serializable;
import java.util.Date;
public class Todo implements Serializable {
private static final long serialVersionUID = 1L;
private String todoId;
private String todoTitle;
private boolean finished;
private Date createdAt;
public String getTodoId() {
return todoId;
}
public void setTodoId(String todoId) {
this.todoId = todoId;
}
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
public boolean isFinished() {
return finished;
}
public void setFinished(boolean finished) {
this.finished = finished;
}
public Date getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}
}
.. figure:: ./images/image058.png
:width: 40%
|
.. note::
Getter/Setter can be generated automatically. After defining fields, right click ``Source`` -> ``Generate Getter and Setters…``
.. figure:: ./images/image059.png
:width: 40%
Click ``OK`` after selecting all other than serialVersionUID
.. figure:: ./images/image060.png
:width: 40%
Repository creation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Following are the steps of CRUD operations pertaining to TODO object required in the current application.
* Fetch 1 record of TODO
* Fetch all records of TODO
* Delete 1 record of TODO
* Update 1 record of TODO
* Fetch record count of completed TODO
Create interface TodoRepository that defines these operations.
FQCN should be ``todo.domain.repository.todo.TodoRepository``.
.. code-block:: java
package todo.domain.repository.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoRepository {
Todo findOne(String todoId);
Collection findAll();
Todo save(Todo todo);
void delete(Todo todo);
long countByFinished(boolean finished);
}
.. figure:: ./images/image061.png
:width: 40%
|
.. note::
Here, to improve versatility of TodoRepository, method is defined to fetch ``record count having x completion status`` and not ``Fetch completed record count``.
Creation of RepositoryImpl (Infrastructure layer)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| For simplification, in-memory implementation that uses Map as the implementation of Repository is used.
| Repository implementation using DB is described in \ :ref:`tutorial-todo_infra`\ .
| FQCN should be ``todo.domain.repository.todo.TodoRepositoryImpl``.
| \ ``@Repository``\ annotation must be used at class level.
.. code-block:: java
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 TODO_MAP = new ConcurrentHashMap();
@Override
public Todo findOne(String todoId) {
return TODO_MAP.get(todoId);
}
@Override
public Collection findAll() {
return TODO_MAP.values();
}
@Override
public Todo save(Todo todo) {
return TODO_MAP.put(todo.getTodoId(), todo);
}
@Override
public void delete(Todo todo) {
TODO_MAP.remove(todo.getTodoId());
}
@Override
public long countByFinished(boolean finished) {
long count = 0;
for (Map.Entry e : TODO_MAP.entrySet()) {
Todo todo = e.getValue();
if (finished == todo.isFinished()) {
count++;
}
}
return count;
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | To consider Repository as component scan target, add \ ``@Repository``\ annotation at class level.
Since the business rules must not be included in Repository, it should focus only on inserting and removing information from the persistence store (here, it is Map).
.. figure:: ./images/image062.png
:width: 40%
\
.. note::
If package is divided completed on the basis of layers, it is better create classes of infrastructure layer under ``todo.infrastructure``.
However, in a normal project, infrastructure layer rarely changes.
Hence, in order to improve the work efficiency, RepositoryImpl can be created in the layer same as the repository of domain layer.
Service creation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Implement business logic. The required processes are as follows.
* Fetch all records of Todo
* New creation of Todo
* Todo completion
* Todo deletion
First, create TodoService interface and then define the above.
FQCN shold be ``todo.domain.serivce.todo.TodoService``.
.. code-block:: java
package todo.domain.service.todo;
import java.util.Collection;
import todo.domain.model.Todo;
public interface TodoService {
Collection findAll();
Todo create(Todo todo);
Todo finish(String todoId);
void delete(String todoId);
}
The required processes and the corresponding implementation methods are as follows
* Fetch all records of Todo→ findAll method
* New creation of Todo→create method
* Todo completion→finish method
* Todo deletion→delete method
.. figure:: ./images/image063.png
:width: 40%
FQCN of implementation class should be ``todo.domain.service.TodoServiceImpl``.
.. code-block:: 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// (1)
// @Transactional // (2)
public class TodoServiceImpl implements TodoService {
@Inject// (3)
protected TodoRepository todoRepository;
private static final long MAX_UNFINISHED_COUNT = 5;
// (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
public Collection 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 + "."));
// (7)
throw new BusinessException(messages);
}
// (8)
String todoId = UUID.randomUUID().toString();
Date createdAt = new Date();
todo.setTodoId(todoId);
todo.setCreatedAt(createdAt);
todo.setFinished(false);
todoRepository.save(todo);
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.save(todo);
return todo;
}
@Override
public void delete(String todoId) {
Todo todo = findOne(todoId);
todoRepository.delete(todo);
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | To consider Service as component-scan target, add ``@Service``\ at class level.
* - | (2)
- | DB is not used in the current implementation, hence transaction management is not required, but when DB is to be used, ``@Transactional``\ should be added at class level.
| It is described in \ :ref:`tutorial-todo_infra`\ .
* - | (3)
- | Inject TodoRepository implementation using \ ``@Inject``\ .
* - | (4)
- | Logic fetch a single record is used in both, delete and finish method. Hence it should be implemented in a method (OK to make it public by declaring in the interface).
* - | (5)
- | Use ``org.terasoluna.gfw.common.message.ResultMessage`` provided in common library, as a class that stores result messages.
| Currently, for throwing error message, ResultMessage is added by specifying message type using ``ResultMessages.error()``.
* - | (6)
- | When target data is not found, ``org.terasoluna.gfw.common.exception.ResourceNotFoundException`` provided in common library is thrown.
* - | (7)
- | When business error occurs, ``org.terasoluna.gfw.common.exception.BusinessException`` provided in common library is thrown.
* - | (8)
- | UUID is used to generate a unique value. DB sequence may be used.
\
.. note::
In this chapter, error message is 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 message externally in property file is described in \ :doc:`../ArchitectureInDetail/PropertyManagement`\ .
.. figure:: ./images/image064.png
:width: 40%
Creation of JUnit for Service
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TBD
Creation of application layer
--------------------------------------------------------------------------------
Since domain layer implementation is completed, use the domain layer to create application layer.
Creation of Controller
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First create TodoController that controls screen transition.
FQCN should be ``todo.app.todo.TodoController``.
It should be noted that the higher level package is different from the domain layer.
.. code-block:: java
package todo.app.todo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // (1)
@RequestMapping("todo") // (2)
public class TodoController {
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | In order to make Controller as component-scan target, add ``@Controller`` at class level.
* - | (2)
- | In order to bring all screen transitions handled by TodoController, under ``/todo``, set ``@RequestMapping(“todo”)`` at class level.
.. figure:: ./images/image065.png
:width: 40%
Show all TODO
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Following is performed on this screen.
* Display of new form
* Display of all records of TODO
Form creation
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Form must contain title information. It should be implemented as JavaBean as shown below. FQCN should be ``todo.app.todo.TodoForm``.
.. code-block:: java
package todo.app.todo;
import java.io.Serializable;
public class TodoForm implements Serializable {
private static final long serialVersionUID = 1L;
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
.. figure:: ./images/image066.png
:width: 40%
Implementation of Controller
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Implement ``setUpForm`` method and ``list`` method in TodoController.
.. code-block:: java
:emphasize-lines: 18-32
package todo.app.todo;
import java.util.Collection;
import javax.inject.Inject;
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 // (3)
protected TodoService todoService;
@ModelAttribute // (4)
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list") // (5)
public String list(Model model) {
Collection todos = todoService.findAll();
model.addAttribute("todos", todos); // (6
return "todo/list"; // (7)
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (3)
- | Add ``@Inject`` annotation for injecting ``TodoService`` using DI container. Since instance of type ``TodoService`` managed by DI container is injected,
| as a result, ``TodoServicelmpl`` instance is injected.
* - | (4)
- | Initialize Form. 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.
* - | (5)
- | Map list method to ``/todo/list``. Since @RequestMapping("todo") is being set at class level, only @RequestMapping(value = "list") is required to be set here.
* - | (6)
- | Add Todo list to Model and pass to View.
* - | (7)
- | If ``todo/list`` is returned as View name, ``WEB-INF/views/todo/list.jsp`` will be rendered using ``InternalResourceViewResolver`` defined in spring-mvc.xml.
JSP creation
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Display Model passed from Controller in ``WEB-INF/views/todo/list.jsp``.
First, create buttons except "Finish"`, "Delete".
.. code-block:: jsp
Todo List
Todo List
${f:h(todo.todoTitle)}
${f:h(todo.todoTitle)}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Display form object using tag. Specify name of the form object added to Model by Controller in ``modelAttribute`` attribute.
| ``contextPath`` to be specified in ``action`` attribute can be fetched in ``${pageContext.request.contextPath}``
* - | (2)
- | Bind form property using tag. Property name of form which is specified in ``modelAttribute`` should match with the value of ``path`` attribute.
* - | (3)
- | Display entire list of Todo using ```` tag.
* - | (4)
- | Determine whether to decorate text using strikethrough(text-decoration: line-through;) to display if it is completed (finished).
* - | (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 \ :doc:`../Security/XSS`\ .
| 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.
.. figure:: ./images/image067.png
:width: 40%
Create TODO
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
Next, implement a new business logic after clicking ``Create TODO`` button on List display screen.
Modifications in Controller
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Add ``create`` method to TodoController.
.. code-block:: java
:emphasize-lines: 8,29-31,46-70
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
protected TodoService todoService;
// (8)
@Inject
protected Mapper beanMapper;
@ModelAttribute
public TodoForm setUpForm() {
TodoForm form = new TodoForm();
return form;
}
@RequestMapping(value = "list")
public String list(Model model) {
Collection todos = todoService.findAll();
model.addAttribute("todos", todos);
return "todo/list";
}
@RequestMapping(value = "create", method = RequestMethod.POST) // (9)
public String create(@Valid TodoForm todoForm, BindingResult bindingResult, // (10)
Model model, RedirectAttributes attributes) { // (11)
// (12)
if (bindingResult.hasErrors()) {
return list(model);
}
// (13)
Todo todo = beanMapper.map(todoForm, Todo.class);
try {
todoService.create(todo);
} catch (BusinessException e) {
// (14)
model.addAttribute(e.getResultMessages());
return list(model);
}
// (15)
attributes.addFlashAttribute(ResultMessages.success().add(
ResultMessage.fromText("Created successfully!")));
return "redirect:/todo/list";
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (8)
- | At the time of converting form object into domain object. Inject useful Mapper.
* - | (9)
- | Set \ ``@RequestMapping``\ such that HTTP method corresponds to POST with path ``/todo/create``.
* - | (10)
- | For performing input validation of form, add ``@Valid`` to form argument. Input validation result is stored in the immediate next argument ``BindingResult``.
* - | (11)
- | Return to list screen by redirecting after it is created normally. Add ``RedirectAttributes`` to argument for storing the information to be redirected.
* - | (12)
- | Return to list screen in case of input error. Re-execute ``list`` method as it is necessary to fetch all records of Todo again.
* - | (13)
- | Create Todo object from TodoForm using Mapper. No need to set if the property name of conversion source and destination is the same.
| There is no merit in using Mapper to convert only todoTitle property, but it is very convenient in case of multiple properties.
* - | (14)
- | Execute business logic and in case of ``BusinessException``, add the result message to Model and return to list screen.
* - | (15)
- | Since it is created normally, 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 launched. Since this time 'Created successfully' message is displayed, ResultMessages.success() is used.
Modifications in Form
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
To define input validation rules, add annotations in form class.
.. code-block:: java
:emphasize-lines: 3-4,8-9
package todo.app.todo;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
public class TodoForm {
@NotNull // (1)
@Size(min = 1, max = 30) // (2)
private String todoTitle;
public String getTodoTitle() {
return todoTitle;
}
public void setTodoTitle(String todoTitle) {
this.todoTitle = todoTitle;
}
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (1)
- | Since it is a mandatory item, add ``@NotNull``.
* - | (2)
- | Specify the range for ``@Size`` between 1 - 30 characters.
Modifications in JSP
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Add the tag for dislaying the result message.
.. code-block:: jsp
:emphasize-lines: 16,22
Todo List
Todo List
${f:h(todo.todoTitle)}
${f:h(todo.todoTitle)}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - Sr.No.
- Description
* - | (6)
- | Display result message using ```` tag.
* - | (7)
- | Display errors in case of input error using ```` tag. Match value of ``path`` attribute of ```` with ``path`` attribute of ```` tag.
If form is submitted by entering appropriate value in the form, 'Created successfully' message is displayed as given below.
.. figure:: ./images/image068.png
:width: 40%
.. figure:: ./images/image069.png
:width: 40%
When 6 or more records are registered and business error occurs, error message is displayed.
.. figure:: ./images/image070.png
:width: 40%
If form is submitted by entering null character, the following error message is displayed.
.. figure:: ./images/image071.png
:width: 40%
Customize message display
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
```` result is output by default as follows.
.. code-block:: html
Created successfully!
With the following modifications in style sheet (in