今日の学習時間。
- Day:26
- Today:12h
- Total:167h
学習内容について。
昨日の続きをしていく。
Employee情報の管理機能を作成。
モデルを作成。
src
/ models
package models;
import java.sql.Timestamp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Table(name = "employees")
@NamedQueries({
@NamedQuery(name = "getAllEmployees", query = "SELECT e FROM Employee AS e ORDER BY e.id DESC"),
@NamedQuery(name = "getEmployeesCount", query = "SELECT COUNT(e) FROM Employee AS e"),
@NamedQuery(name = "checkRegisteredCode", query = "SELECT COUNT(e) FROM Employee AS e WHERE e.code = :code"),
@NamedQuery(name = "checkLoginCodeAndPassword", query = "SELECT e FROM Employee AS e WHERE e.delete_flag = 0 AND e.code = :code AND e.password = :pass")
})
@Entity
public class Employee {
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "code", nullable = false, unique = true)
private String code;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "password", length = 64, nullable = false)
private String password;
@Column(name = "admin_flag", nullable = false)
private Integer admin_flag;
@Column(name = "created_at", nullable = false)
private Timestamp created_at;
@Column(name = "updated_at", nullable = false)
private Timestamp updated_at;
@Column(name = "delete_flag", nullable = false)
private Integer delete_flag;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAdmin_flag() {
return admin_flag;
}
public void setAdmin_flag(Integer admin_flag) {
this.admin_flag = admin_flag;
}
public Timestamp getCreated_at() {
return created_at;
}
public void setCreated_at(Timestamp created_at) {
this.created_at = created_at;
}
public Timestamp getUpdated_at() {
return updated_at;
}
public void setUpdated_at(Timestamp updated_at) {
this.updated_at = updated_at;
}
public Integer getDelete_flag() {
return delete_flag;
}
public void setDelete_flag(Integer delete_flag) {
this.delete_flag = delete_flag;
}
}
カラム名 | 用途 | データ型 |
---|
id | リソース内での連番 | 数値型 |
code | 社員番号 | 文字列型 |
name | 社員名 | 文字列型 |
password | システムへのログインパスワード | 文字列型 |
admin_flag | 管理者権限があるかどうか | 数値型(一般:0、管理者:1) |
created_at | 登録日時 | 日時型 |
updated_at | 更新日時 | 日時型 |
delete_flag | 削除された従業員かどうか | 数値型(現役:0、削除済み:1) |
Employee
に設定したプロパティ
@NamedQuery
getAllEmployees
getEmployeesCount
checkRegisteredCode
- 指定された社員番号がすでにデータベースに存在しているかをチェック
checkLoginCodeAndPassword
- 従業員がログインするときに社員番号とパスワードが正しいかをチェック
一意制約
- 28行目:
code
のプロパティにunique = true
という指定を追加- 一意制約という
- すでに存在している社員番号は登録できない旨をデータベースに教えてあげるための設定
- データベースにも制約を入れて重複を防止するのが一般的
id
は単純にEmployee
内での連続した番号という意味合いだけを持つ
length = 64
の意味
- 例えば
length = 64
と入れることで、入力できる文字情報が最大64文字までになる - パスワードの情報については
SHA256
というハッシュ関数を利用- ハッシュ化した文字列をデータベースへ保存できるようにする
- ハッシュ化されていない生のパスワード文字列をデータベースに保存するのはセキュリティ面から考えて危険
SHA256
は、どんな文字数の文字列でも必ず、64文字のハッシュ化された文字列にする
バリデーション用のクラスを作成。
src
/ models
/ validators
package models.validators;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import models.Employee;
import utils.DBUtil;
public class EmployeeValidator {
public static List<String> validate(Employee e, Boolean code_duplicate_check_flag, Boolean password_check_flag) {
List<String> errors = new ArrayList<String>();
String code_error = _validateCode(e.getCode(), code_duplicate_check_flag);
if (!code_error.equals("")) {
errors.add(code_error);
}
String name_error = _validateName(e.getName());
if (!name_error.equals("")) {
errors.add(name_error);
}
String password_error = _validatePassword(e.getPassword(), password_check_flag);
if (!password_error.equals("")) {
errors.add(password_error);
}
return errors;
}
// 社員番号
private static String _validateCode(String code, Boolean code_duplicate_check_flag) {
// 必須入力チェック
if (code == null || code.equals("")) {
return "社員番号を入力してください。";
}
// すでに登録されている社員番号との重複チェック
if (code_duplicate_check_flag) {
EntityManager em = DBUtil.createEntityManager();
long employees_count = (long) em.createNamedQuery("checkRegisteredCode", Long.class)
.setParameter("code", code)
.getSingleResult();
em.close();
if (employees_count > 0) {
return "入力された社員番号の情報はすでに存在しています。";
}
}
return "";
}
// 社員名の必須入力チェック
private static String _validateName(String name) {
if (name == null || name.equals("")) {
return "氏名を入力してください。";
}
return "";
}
// パスワードの必須入力チェック
private static String _validatePassword(String password, Boolean password_check_flag) {
// パスワードを変更する場合のみ実行
if (password_check_flag && (password == null || password.equals(""))) {
return "パスワードを入力してください。";
}
return "";
}
}
- 社員番号、氏名、パスワードの必須入力チェックを行う
- 社員番号のみ「すでにデータベースに存在する社員番号かどうか」のチェック
- 変更(update)の場合は、バリデーションが不要な場合もある(パスワードなど)
- 社員番号とパスワードについては第2引数にBoolean型の引数を用意
true
であれば、パスワードの入力値チェックと社員番号の重複チェックを行う
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee class"
[master 413438b] Add Employee class
3 files changed, 185 insertions(+)
create mode 100644 src/models/Employee.java
create mode 100644 src/models/validators/EmployeeValidator.java
indexを作成。
アクション | サーブレット名 | URLマッピング | HTTPメソッド |
---|
index | EmployeesIndexServlet | /employees/index | GET |
new | EmployeesNewServlet | /employees/new | GET |
create | EmployeesCreateServlet | /employees/create | POST |
show | EmployeesShowServlet | /employees/show | GET |
edit | EmployeesEditServlet | /employees/edit | GET |
update | EmployeesUpdateServlet | /employees/update | POST |
destroy | EmployeesDestroyServlet | /employees/destroy | POST |
上記の表に沿って7つのアクションを作成していく。
servletを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import java.util.List;
import javax.persistence.EntityManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import utils.DBUtil;
/**
* Servlet implementation class EmployeesIndexServlet
*/
@WebServlet("/employees/index")
public class EmployeesIndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesIndexServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
EntityManager em = DBUtil.createEntityManager();
int page = 1;
try {
page = Integer.parseInt(request.getParameter("page"));
} catch (NumberFormatException e) {
}
List<Employee> employees = em.createNamedQuery("getAllEmployees", Employee.class)
.setFirstResult(15 * (page - 1))
.setMaxResults(15)
.getResultList();
long employees_count = (long) em.createNamedQuery("getEmployeesCount", Long.class)
.getSingleResult();
em.close();
request.setAttribute("employees", employees);
request.setAttribute("employees_count", employees_count);
request.setAttribute("page", page);
if (request.getSession().getAttribute("flush") != null) {
request.setAttribute("flush", request.getSession().getAttribute("flush"));
request.getSession().removeAttribute("flush");
}
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/index.jsp");
rd.forward(request, response);
}
}
JSPを作成。
WebContent
/ WEB-INF
/ views
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:import url="../layout/app.jsp">
<c:param name="content">
<c:if test="${flush != null}">
<div id="flush_success">
<c:out value="${flush}"></c:out>
</div>
</c:if>
<h2>従業員 一覧</h2>
<table id="employee_list">
<tbody>
<tr>
<th>社員番号</th>
<th>氏名</th>
<th>操作</th>
</tr>
<c:forEach var="employee" items="${employees}" varStatus="status">
<tr class="row${status.count % 2}">
<td><c:out value="${employee.code}" /></td>
<td><c:out value="${employee.name}" /></td>
<td>
<c:choose>
<c:when test="${employee.delete_flag == 1}">
(削除済み)
</c:when>
<c:otherwise>
<a href="<c:url value='/employees/show?id=${employee.id}' />">詳細を表示</a>
</c:otherwise>
</c:choose>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<div id="pagination">
(全 ${employees_count} 件)<br />
<c:forEach var="i" begin="1" end="${((employees_count - 1) / 15) + 1}" step="1">
<c:choose>
<c:when test="${i == page}">
<c:out value="${i}" />
</c:when>
<c:otherwise>
<a href="<c:url value='/employees/index?page=${i}' />"><c:out value="${i}" /></a>
</c:otherwise>
</c:choose>
</c:forEach>
</div>
<p><a href="<c:url value='/employees/new' />">新規従業員の登録</a></p>
</c:param>
</c:import>
style.css
に追加
table#employee_list th {
width: 30%;
padding: 10px 2%;
}
table#employee_list td {
width: 29%;
padding: 10px 2%;
}
tr.row1 {
background-color: #f2f2f2;
}
tr.row0 {
background-color: #ffffff;
}
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee index"
[master c957b78] Add Employee index
2 files changed, 118 insertions(+)
create mode 100644 WebContent/WEB-INF/views/employees/index.jsp
create mode 100644 src/controllers/employees/EmployeesIndexServlet.java
newを作成。
servletを作成
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
/**
* Servlet implementation class EmployeesNewServlet
*/
@WebServlet("/employees/new")
public class EmployeesNewServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesNewServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("_token", request.getSession().getId());
request.setAttribute("employee", new Employee());
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/new.jsp");
rd.forward(request, response);
}
}
JSPを作成。
new.jsp
とedit.jsp
で共通しているフォーム部分は_form.jsp
というファイルで共通化する。
WebContent
/ WEB-INF
/ views
/ employees
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:if test="${errors != null}">
<div id="flush_error">
入力内容にエラーがあります。<br />
<c:forEach var="error" items="${errors}">
・<c:out value="${error}" /><br />
</c:forEach>
</div>
</c:if>
<label for="code">社員番号</label><br />
<input type="text" name="code" value="${employee.code}" />
<br /><br />
<label for="name">氏名</label><br />
<input type="text" name="name" value="${employee.name}" />
<br /><br />
<label for="password">パスワード</label><br />
<input type="password" name="password" />
<br /><br />
<label for="admin_flag">権限</label><br />
<select name="admin_flag">
<option value="0"<c:if test="${employee.admin_flag == 0}"> selected</c:if>>一般</option>
<option value="1"<c:if test="${employee.admin_flag == 1}"> selected</c:if>>管理者</option>
</select>
<br /><br />
<input type="hidden" name="_token" value="${_token}" />
<button type="submit">投稿</button>
WebContent
/ WEB-INF
/ views
/ employees
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:import url="../layout/app.jsp">
<c:param name="content">
<h2>従業員 新規登録ページ</h2>
<form method="POST" action="<c:url value='/employees/create' />">
<c:import url="_form.jsp" />
</form>
<p><a href="<c:url value='/employees/index' />">一覧に戻る</a></p>
</c:param>
</c:import>
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee new"
[master 46b3958] Add Employee new
4 files changed, 90 insertions(+)
create mode 100644 WebContent/WEB-INF/views/employees/_form.jsp
create mode 100644 WebContent/WEB-INF/views/employees/new.jsp
create mode 100644 src/controllers/employees/EmployeesNewServlet.java
createを作成。
パスワードのソルト文字列をリスナーを使って登録。
create
を作成する前に、パスワードのハッシュ化に関する新しいクラスを2つ用意- 入力されたパスワード文字列に何らかの文字列を連結させたものを
SHA256
でハッシュ化 - ハッシュ化した文字列をデータベースに登録
- 上記はパスワード文字列をそのままハッシュ化するよりも安全にパスワードデータを保管するための工夫
- パスワードに連結させる文字列のことをソルト文字列という
- パスワードのソルト文字列をリスナーを使って登録(下記)
- イベントはjavax.servlet.ServletContextListenerのみにチェック
src
/ listeners
package listeners;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.Properties;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
/**
* Application Lifecycle Listener implementation class PropertiesListener
*
*/
@WebListener
public class PropertiesListener implements ServletContextListener {
/**
* Default constructor.
*/
public PropertiesListener() {
// TODO Auto-generated constructor stub
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent arg0) {
ServletContext context = arg0.getServletContext();
String path = context.getRealPath("/META-INF/application.properties");
try {
InputStream is = new FileInputStream(path);
Properties properties = new Properties();
properties.load(is);
is.close();
Iterator<String> pit = properties.stringPropertyNames().iterator();
while (pit.hasNext()) {
String pname = pit.next();
context.setAttribute(pname, properties.getProperty(pname));
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
}
- このファイルが外部から見えてしまうとセキュリティ対策が無意味になる
- GitHubにpushする場合を考えて、このファイルのパスを
.gitignore
に追加してGit管理の対象外した
文字列をSHA256でハッシュ化するクラスを作成。
getPasswordEncrypt(src)
のようにメソッド名をひとつ記述すれば実行できるハッシュ化メソッドを用意(下記)してユーティリティにする。
src
/ utils
package utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
public class EncryptUtil {
public static String getPasswordEncrypt(String plain_p, String salt) {
String ret = "";
if (plain_p != null && !plain_p.equals("")) {
byte[] bytes;
String password = plain_p + salt;
try {
bytes = MessageDigest.getInstance("SHA-256").digest(password.getBytes());
ret = DatatypeConverter.printHexBinary(bytes);
} catch (NoSuchAlgorithmException ex) {
}
}
return ret;
}
}
getPasswordEncrypt
メソッド- 引数で受け取った文字列にソルト文字列を連結させたものを
SHA256
でハッシュ化 - 引数の文字列が何もなければ、空の文字列を返す
ここまでをGitにコミット
$ git add .
$ git commit -m "Add password encryption"
[master aa8d566] Add password encryption
2 files changed, 83 insertions(+)
create mode 100644 src/listeners/PropertiesListener.java
create mode 100644 src/utils/EncryptUtil.java
create用のservletを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;
import javax.persistence.EntityManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import models.validators.EmployeeValidator;
import utils.DBUtil;
import utils.EncryptUtil;
/**
* Servlet implementation class EmployeesCreateServlet
*/
@WebServlet("/employees/create")
public class EmployeesCreateServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesCreateServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String _token = (String) request.getParameter("_token");
if (_token != null && _token.equals(request.getSession().getId())) {
EntityManager em = DBUtil.createEntityManager();
Employee e = new Employee();
e.setCode(request.getParameter("code"));
e.setName(request.getParameter("name"));
e.setPassword(
EncryptUtil.getPasswordEncrypt(
request.getParameter("password"),
(String) this.getServletContext().getAttribute("salt")));
e.setAdmin_flag(Integer.parseInt(request.getParameter("admin_flag")));
Timestamp currentTime = new Timestamp(System.currentTimeMillis());
e.setCreated_at(currentTime);
e.setUpdated_at(currentTime);
e.setDelete_flag(0);
List<String> errors = EmployeeValidator.validate(e, true, true);
if (errors.size() > 0) {
em.close();
request.setAttribute("_token", request.getSession().getId());
request.setAttribute("employee", e);
request.setAttribute("errors", errors);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/new.jsp");
rd.forward(request, response);
} else {
em.getTransaction().begin();
em.persist(e);
em.getTransaction().commit();
em.close();
request.getSession().setAttribute("flush", "登録が完了しました。");
response.sendRedirect(request.getContextPath() + "/employees/index");
}
}
}
}
- 59行目
- 新規登録の場合、パスワードの入力値チェックと社員番号の重複チェックは必ず実施
- 第2と第3引数を両方とも
true
で指定
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee create"
[master fe1bc7f] Add Employee create
1 file changed, 81 insertions(+)
create mode 100644 src/controllers/employees/EmployeesCreateServlet.java
showを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import javax.persistence.EntityManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import utils.DBUtil;
/**
* Servlet implementation class EmployeesShowServlet
*/
@WebServlet("/employees/show")
public class EmployeesShowServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesShowServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
EntityManager em = DBUtil.createEntityManager();
Employee e = em.find(Employee.class, Integer.parseInt(request.getParameter("id")));
em.close();
request.setAttribute("employee", e);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/show.jsp");
rd.forward(request, response);
}
}
WebContent
/ WEB-INF
/ views
/ employees
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<c:import url="/WEB-INF/views/layout/app.jsp">
<c:param name="content">
<c:choose>
<c:when test="${employee != null}">
<h2>id : ${employee.id} の従業員情報 詳細ページ</h2>
<table>
<tbody>
<tr>
<th>社員番号</th>
<td><c:out value="${employee.code}" /></td>
</tr>
<tr>
<th>氏名</th>
<td><c:out value="${employee.name}" /></td>
</tr>
<tr>
<th>権限</th>
<td>
<c:choose>
<c:when test="${employee.admin_flag == 1}">管理者</c:when>
<c:otherwise>一般</c:otherwise>
</c:choose>
</td>
</tr>
<tr>
<th>登録日時</th>
<td>
<fmt:formatDate value="${employee.created_at}" pattern="yyyy-MM-dd HH:mm:ss" />
</td>
</tr>
<tr>
<th>更新日時</th>
<td>
<fmt:formatDate value="${employee.updated_at}" pattern="yyyy-MM-dd HH:mm:ss" />
</td>
</tr>
</tbody>
</table>
<p><a href="<c:url value='/employees/edit?id=${employee.id}' />">この従業員情報を編集する</a></p>
</c:when>
<c:otherwise>
<h2>お探しのデータは見つかりませんでした。</h2>
</c:otherwise>
</c:choose>
<p><a href="<c:url value='/employees/index' />">一覧に戻る</a></p>
</c:param>
</c:import>
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee show"
[master bdfeb60] Add Employee show
2 files changed, 101 insertions(+)
create mode 100644 WebContent/WEB-INF/views/employees/show.jsp
create mode 100644 src/controllers/employees/EmployeesShowServlet.java
editを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import javax.persistence.EntityManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import utils.DBUtil;
/**
* Servlet implementation class EmployeesEditServlet
*/
@WebServlet("/employees/edit")
public class EmployeesEditServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesEditServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
EntityManager em = DBUtil.createEntityManager();
Employee e = em.find(Employee.class, Integer.parseInt(request.getParameter("id")));
em.close();
request.setAttribute("employee", e);
request.setAttribute("_token", request.getSession().getId());
request.getSession().setAttribute("employee_id", e.getId());
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/edit.jsp");
rd.forward(request, response);
}
}
WebContent
/ WEB-INF
/ views
/ employees
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:import url="/WEB-INF/views/layout/app.jsp">
<c:param name="content">
<c:choose>
<c:when test="${employee != null}">
<h2>id : ${employee.id} の従業員情報 編集ページ</h2>
<p>(パスワードは変更する場合のみ入力してください)</p>
<form method="POST" action="<c:url value='/employees/update' />">
<c:import url="_form.jsp" />
</form>
<p><a href="#" onclick="confirmDestroy();">この従業員情報を削除する</a></p>
<form method="POST" action="<c:url value='/employees/destroy' />">
<input type="hidden" name="_token" value="${_token}" />
</form>
<script>
function confirmDestroy() {
if(confirm("本当に削除してよろしいですか?")) {
document.forms[1].submit();
}
}
</script>
</c:when>
<c:otherwise>
<h2>お探しのデータは見つかりませんでした。</h2>
</c:otherwise>
</c:choose>
<p><a href="<c:url value='/employees/index' />">一覧に戻る</a></p>
</c:param>
</c:import>
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee edit"
[master 20d950c] Add Employee edit
2 files changed, 82 insertions(+)
create mode 100644 WebContent/WEB-INF/views/employees/edit.jsp
create mode 100644 src/controllers/employees/EmployeesEditServlet.java
updateを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;
import javax.persistence.EntityManager;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import models.validators.EmployeeValidator;
import utils.DBUtil;
import utils.EncryptUtil;
/**
* Servlet implementation class EmployeesUpdateServlet
*/
@WebServlet("/employees/update")
public class EmployeesUpdateServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesUpdateServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String _token = (String) request.getParameter("_token");
if (_token != null && _token.equals(request.getSession().getId())) {
EntityManager em = DBUtil.createEntityManager();
Employee e = em.find(Employee.class, (Integer) (request.getSession().getAttribute("employee_id")));
// 現在の値と異なる社員番号が入力されていたら
// 重複チェックを行う指定をする
Boolean code_duplicate_check = true;
if (e.getCode().equals(request.getParameter("code"))) {
code_duplicate_check = false;
} else {
e.setCode(request.getParameter("code"));
}
// パスワード欄に入力があったら
// パスワードの入力値チェックを行う指定をする
Boolean password_check_flag = true;
String password = request.getParameter("password");
if (password == null || password.equals("")) {
password_check_flag = false;
} else {
e.setPassword(
EncryptUtil.getPasswordEncrypt(
password,
(String) this.getServletContext().getAttribute("salt")));
}
e.setName(request.getParameter("name"));
e.setAdmin_flag(Integer.parseInt(request.getParameter("admin_flag")));
e.setUpdated_at(new Timestamp(System.currentTimeMillis()));
e.setDelete_flag(0);
List<String> errors = EmployeeValidator.validate(e, code_duplicate_check, password_check_flag);
if (errors.size() > 0) {
em.close();
request.setAttribute("_token", request.getSession().getId());
request.setAttribute("employee", e);
request.setAttribute("errors", errors);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/employees/edit.jsp");
rd.forward(request, response);
} else {
em.getTransaction().begin();
em.getTransaction().commit();
em.close();
request.getSession().setAttribute("flush", "更新が完了しました。");
request.getSession().removeAttribute("employee_id");
response.sendRedirect(request.getContextPath() + "/employees/index");
}
}
}
}
- 社員番号は現在と異なる内容が入力された(変更を希望した)場合に重複チェック
- パスワードは入力欄に入力された際、入力値チェックを実行
- 今後このシステムを拡張していく場合のことを考慮した実装
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee update"
[master bc29e2c] Add Employee update
1 file changed, 96 insertions(+)
create mode 100644 src/controllers/employees/EmployeesUpdateServlet.java
destroyを作成。
src
/ controllers
/ employees
package controllers.employees;
import java.io.IOException;
import java.sql.Timestamp;
import javax.persistence.EntityManager;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import utils.DBUtil;
/**
* Servlet implementation class EmployeesDestroyServlet
*/
@WebServlet("/employees/destroy")
public class EmployeesDestroyServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public EmployeesDestroyServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String _token = (String) request.getParameter("_token");
if (_token != null && _token.equals(request.getSession().getId())) {
EntityManager em = DBUtil.createEntityManager();
Employee e = em.find(Employee.class, (Integer) (request.getSession().getAttribute("employee_id")));
e.setDelete_flag(1);
e.setUpdated_at(new Timestamp(System.currentTimeMillis()));
em.getTransaction().begin();
em.getTransaction().commit();
em.close();
request.getSession().setAttribute("flush", "削除が完了しました。");
response.sendRedirect(request.getContextPath() + "/employees/index");
}
}
}
論理削除
- 今回は
remove
メソッドは使用していない- 代わりに
Employee
プロパティのひとつであるdelete_flag
が1になっている従業員情報は削除されているとみなすルールでシステムを作成
- 理由:日報を残したまま従業員だけ削除されてしまうと、提出者が不明の日報情報が溜まってしまうから
- 論理削除
destroy
した従業員情報は削除したとみなしてシステム上で扱う
delete
でデータ自体を削除するか、delete_flag
で論理削除をするかは状況判断が必要
ここまでをGitにコミット
$ git add .
$ git commit -m "Add Employee destroy"
[master e4e5b94] Add Employee destroy
1 file changed, 53 insertions(+)
create mode 100644 src/controllers/employees/EmployeesDestroyServlet.java
ログイン機能を実装。
- システムに登録されている従業員だけが利用できるように、ログイン機能を追加
- 従業員管理のページは管理者(
admin_flag
が1
になっているアカウント)のみが閲覧可能に
ログインとログアウトのサーブレットを作成。
アクション | サーブレット名 | URLマッピング | HTTPメソッド |
---|
login | LoginServlet | /login | GET, POST |
logout | LogoutServlet | /logout | GET |
src
/ controllers
/ login
package controllers.login;
import java.io.IOException;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import models.Employee;
import utils.DBUtil;
import utils.EncryptUtil;
/**
* Servlet implementation class LoginServlet
*/
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public LoginServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
// ログイン画面を表示
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setAttribute("_token", request.getSession().getId());
request.setAttribute("hasError", false);
if (request.getSession().getAttribute("flush") != null) {
request.setAttribute("flush", request.getSession().getAttribute("flush"));
request.getSession().removeAttribute("flush");
}
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/login/login.jsp");
rd.forward(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
// ログイン処理を実行
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 認証結果を格納する変数
Boolean check_result = false;
String code = request.getParameter("code");
String plain_pass = request.getParameter("password");
Employee e = null;
if (code != null && !code.equals("") && plain_pass != null && !plain_pass.equals("")) {
EntityManager em = DBUtil.createEntityManager();
String password = EncryptUtil.getPasswordEncrypt(
plain_pass,
(String) this.getServletContext().getAttribute("salt"));
// 社員番号とパスワードが正しいかチェックする
try {
e = em.createNamedQuery("checkLoginCodeAndPassword", Employee.class)
.setParameter("code", code)
.setParameter("pass", password)
.getSingleResult();
} catch (NoResultException ex) {
}
em.close();
if (e != null) {
check_result = true;
}
}
if (!check_result) {
// 認証できなかったらログイン画面に戻る
request.setAttribute("_token", request.getSession().getId());
request.setAttribute("hasError", true);
request.setAttribute("code", code);
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/login/login.jsp");
rd.forward(request, response);
} else {
// 認証できたらログイン状態にしてトップページへリダイレクト
request.getSession().setAttribute("login_employee", e);
request.getSession().setAttribute("flush", "ログインしました。");
response.sendRedirect(request.getContextPath() + "/");
}
}
}
- ログイン画面の表示は
GET
、認証処理はPOST
doPost()
- ログインページで入力された社員番号とパスワードをもとにデータベースへ照合
- 情報に間違いがなければセッションスコープにその従業員情報のオブジェクトを格納(
login_employee
へ) - セッションスコープに
login_employee
という名前で従業員情報のオブジェクトが保存されている状態がログイン状態
- パスワードはハッシュ化されてデータベースに登録されている
- フォームから入力されたパスワードを67行目でソルト文字列に連結した文字列をハッシュ化する
- そのデータとデータベース上のデータで照合を行う
src
/ controllers
/ login
package controllers.login;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class LogoutServlet
*/
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public LogoutServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getSession().removeAttribute("login_employee");
request.getSession().setAttribute("flush", "ログアウトしました。");
response.sendRedirect(request.getContextPath() + "/login");
}
}
- セッションスコープから
login_employee
を除去することでログアウトした状態にする(31行目) - ログアウトしたら、自動でログインページにリダイレクトされる(34行目)
WebContent
/ WEB-INF
/ views
/ login
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:import url="/WEB-INF/views/layout/app.jsp">
<c:param name="content">
<c:if test="${hasError}">
<div id="flush_error">
社員番号かパスワードが間違っています。
</div>
</c:if>
<c:if test="${flush != null}">
<div id="flush_success">
<c:out value="${flush}"></c:out>
</div>
</c:if>
<h2>ログイン</h2>
<form method="POST" action="<c:url value='/login' />">
<label for="code">社員番号</label><br />
<input type="text" name="code" value="${code}" />
<br /><br />
<label for="password">パスワード</label><br />
<input type="password" name="password" />
<br /><br />
<input type="hidden" name="_token" value="${_token}" />
<button type="submit">ログイン</button>
</form>
</c:param>
</c:import>
hasError
という変数がリクエストスコープにtrue
でセットされていればエラーメッセージを表示する- サーブレットからメッセージをリクエストスコープにセットするのではなく、あらかじめJSPに記述
ここまでをGitにコミット
$ git add .
$ git commit -m "Add login / logout"
[master b59c7b9] Add login / logout
3 files changed, 170 insertions(+)
create mode 100644 WebContent/WEB-INF/views/login/login.jsp
create mode 100644 src/controllers/login/LoginServlet.java
create mode 100644 src/controllers/login/LogoutServlet.java
ログインチェック用のフィルタを作成。
filters
パッケージにLoginFilter
フィルタを追加- フィルター・マッピングは
/*
- ログイン状態のチェックは、サーブレットの処理よりも前に実行
- ログインチェック用のフィルタを実装する前に、必ず管理者 (
admin_flag
が1
になっているアカウント) を1つ作成しておく- フィルタ実装後は管理者しか従業員管理 (
/emploees
) のページにアクセスできない
src
/ filters
package filters;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import models.Employee;
/**
* Servlet Filter implementation class LoginFilter
*/
@WebFilter("/*")
public class LoginFilter implements Filter {
/**
* Default constructor.
*/
public LoginFilter() {
// TODO Auto-generated constructor stub
}
/**
* @see Filter#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String context_path = ((HttpServletRequest)request).getContextPath();
String servlet_path = ((HttpServletRequest)request).getServletPath();
if(!servlet_path.matches("/css.*")) { // CSSフォルダ内は認証処理から除外する
HttpSession session = ((HttpServletRequest)request).getSession();
// セッションスコープに保存された従業員(ログインユーザ)情報を取得
Employee e = (Employee)session.getAttribute("login_employee");
if(!servlet_path.equals("/login")) { // ログイン画面以外について
// ログアウトしている状態であれば
// ログイン画面にリダイレクト
if(e == null) {
((HttpServletResponse)response).sendRedirect(context_path + "/login");
return;
}
// 従業員管理の機能は管理者のみが閲覧できるようにする
if(servlet_path.matches("/employees.*") && e.getAdmin_flag() == 0) {
((HttpServletResponse)response).sendRedirect(context_path + "/");
return;
}
} else { // ログイン画面について
// ログインしているのにログイン画面を表示させようとした場合は
// システムのトップページにリダイレクト
if(e != null) {
((HttpServletResponse)response).sendRedirect(context_path + "/");
return;
}
}
}
chain.doFilter(request, response);
}
/**
* @see Filter#init(FilterConfig)
*/
public void init(FilterConfig fConfig) throws ServletException {
// TODO Auto-generated method stub
}
}
ここまでをGitにコミット
$ git add .
$ git commit -m "Add LoginFilter"
[master 1a7306c] Add LoginFilter
1 file changed, 50 insertions(+)
create mode 100644 src/filters/LoginFilter.java
画面を調整。
- ログイン後のトップページにフラッシュメッセージを表示
- 画面にログアウトへのリンクを表示する
src
/ controllers
/ toppage
↓modified
package controllers.toppage;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class TopPageIndexServlet
*/
@WebServlet("/index.html")
public class TopPageIndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public TopPageIndexServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (request.getSession().getAttribute("flush") != null) {
request.setAttribute("flush", request.getSession().getAttribute("flush"));
request.getSession().removeAttribute("flush");
}
RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/views/topPage/index.jsp");
rd.forward(request, response);
}
}
上記でリクエストスコープにフラッシュメッセージを設定した。下記はそのビュー。
WebContent
/ WEB-INF
/ views
/ topPage
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:import url="../layout/app.jsp">
<c:param name="content">
<c:if test="${flush != null}">
<div id="flush_success">
<c:out value="${flush}"></c:out>
</div>
</c:if>
<h2>日報管理システムへようこそ</h2>
</c:param>
</c:import>
ログイン中の画面にログアウトへのリンクなどを表示するように下記を修正した。
WebContent
/ WEB-INF
/ views
/ layout
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>日報管理システム</title>
<link rel="stylesheet" href="<c:url value='/css/reset.css' />">
<link rel="stylesheet" href="<c:url value='/css/style.css' />">
</head>
<body>
<div id="wrapper">
<div id="header">
<div id="header_menu">
<h1><a href="<c:url value='/' />">日報管理システム</a></h1>
<c:if test="${sessionScope.login_employee != null}">
<c:if test="${sessionScope.login_employee.admin_flag == 1}">
<a href="<c:url value='/employees/index' />">従業員管理</a>
</c:if>
<a href="<c:url value='/reports/index' />">日報管理</a>
</c:if>
</div>
<c:if test="${sessionScope.login_employee != null}">
<div id="employee_name">
<c:out value="${sessionScope.login_employee.name}" /> さん
<a href="<c:url value='/logout' />">ログアウト</a>
</div>
</c:if>
</div>
<div id="content">
${param.content}
</div>
<div id="footer">
by Taro Kirameki.
</div>
</div>
</body>
</html>
style.css
に追加
#header_menu {
width: 57%;
padding-top: 17px;
padding-left: 3%;
display: inline-block;
}
#employee_name {
color: #cccccc;
width: 36%;
padding-right: 3%;
text-align: right;
display: inline-block;
}
#header a {
color: #eeeeee;
}
h1 a {
color: #eeeeee;
}
/* 上書き */
h1 {
font-size: 24px;
display: inline;
}
ここまでをGitにコミット
$ git add .
$ git commit -m "Add login / logout link"
[master 9ece3ec] Add login / logout link
4 files changed, 58 insertions(+), 13 deletions(-)
今日の反省と明日の目標。
プログラミングしてるとあっという間に1日が終わってしまうことに毎度驚いているところです。先週に痛めた首が未だに痛くてジムに行けてない分、コードを書く活力へと変換しています。さすがに病院に行くべきかな。そろそろ温泉も行きたいな。
さて、計画では明日でカリキュラムが終わりそうです。学習を通じて、前よりも自分が何を勉強していくべきかがより鮮明になった気がしています。ポートフォリオを作るにあたってはフロントエンドの技術も必要でしょうし。Javaの一連の流れは掴めてきたと思っていますが、これをどう応用していくかが悩ましいですね。いろいろ作ってみたいものはあるにはあるんです。しかし、それを自分の実力で作れるか。。まあ、その分からないところをメンターの方に聞くために早めにカリキュラムを終わらせてるので、大丈夫だろうと思います、そう信じたい。
明日はDTOを使った日報情報の管理機能について学習していきます。
一応ですが、今のところ興味があるのは、JavaScript、Linux、AWS、Dockerです。それらの技術も並行して学習していく予定。というよりも、そんなに時間がないので作りたいものだけ調べて引っ張ってくる形で。