Java EE 8 Design Patterns and Best Practices
上QQ阅读APP看书,第一时间看更新

Implementing the Session Façade pattern

Let's make a small application related to the academic world. We will make two fa?ades—one fa?ade to manage the financial part of the application, and one to manage the academic part of the application. We will also construct some other classes, such as DAO classes and classes of the domain model. There is no database; all the data is kept in memory through the DAO classes. Consequently, the methods designed for finding information are built into the DAO classes. Let's create the following domain model classes: Discipline, Course, Member (Member is an abstract class that represents a member of a college), Professor, and Student:

import java.io.Serializable;

public class Discipline implements Serializable{
private String name;
private String code;

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((code == null) ? 0 : code.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;
Discipline other = (Discipline) obj;
if (code == null) {
if (other.code != null)
return false;
} else if (!code.equals(other.code))
return false;
return true;
}
public Discipline() {
}
public Discipline(String code, String name) {
this.setCode(code);
this.setName(name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}

This is the Course class:

 import java.io.Serializable;

public class Course implements Serializable {
private String code;
private String name;
public Course() {
}
public Course (String code, String name) {
this.setCode(code);
this.setName(name);
}
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;
}
}

Here is the Member class which like the previous classes, implements the Serializable package:

import java.io.Serializable;
import java.time.LocalDate;

public abstract class Member implements Serializable {

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.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;
Member other = (Member) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
private String name;
private LocalDate initDate;
private String email;

public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getInitDate() {
return initDate;
}
public void setInitDate(LocalDate initDate) {
this.initDate = initDate;
}
}

Now, the Professor class inherits the Member class:

import java.util.Date;
import java.time.LocalDate;

public class Professor extends Member {
private LocalDate initTeachDate;
public Professor() {
}
public Professor(String name, LocalDate initDate) {
this.setName(name);
this.setInitDate(initDate);
}
public Professor(String name) {
this.setName(name);
}
public LocalDate getInitTeachDate() {
return initTeachDate;
}
public void setInitTeachDate(LocalDate initTeachDate) {
this.initTeachDate = initTeachDate;
}
}

The following is the Student class, which inherits the Member class:

public class Student extends Member {
private String enrollment;
public Student() {
}
public Student(String enrollment) {
this.setEnrollment(enrollment);
}
public Student(String enrollment, String name) {
this.setEnrollment(enrollment);
this.setName(name);
}
public String getEnrollment() {
return enrollment;
}
public void setEnrollment(String enrollment) {
this.enrollment = enrollment;
}
}

We could make these application entities with an id integer type property that would represent a unique entity. It is common to extend an abstract entity class that contains this ID. However, for the college members, we simplified it and used the name property for the identification job. In the Discipline and Member classes, we implemented the equals method to check for equal objects within a collection. 

Let's make some DAO classes. There is no POJO JPA entity in these examples. The relationships between the model objects are inserted in the DAO classes:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CourseDAO {
private static Map<Course, List<Discipline>> courseXDisciplines;
static {
Discipline d1 = new Discipline("D1", "discipline 1");
Discipline d2 = new Discipline("D2", "discipline 2");
Discipline d3 = new Discipline("D3", "discipline 3");
Discipline d4 = new Discipline("D4", "discipline 4");

courseXDisciplines = new HashMap<>();
courseXDisciplines.put (new Course ("C1", "Course 1"), Arrays.asList (d1, d2, d4));
courseXDisciplines.put (new Course ("C2", "Course 2"), Arrays.asList (d1, d3));
courseXDisciplines.put (new Course ("C3", "Course 3"), Arrays.asList (d2, d3, d4));
}

public List<Discipline> getDisciplinesByCourse(Course course) {
return courseXDisciplines.get(course);
}
}

This is the DisciplineDAO class:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class DisciplineDAO {
private static Map<Discipline, List<Discipline>> disciplineXPreReqDisciplines = new HashMap<>();
private static Map<Professor, List<Discipline>> professorXDisciplines = new HashMap<>();
private static Map<Discipline, List<String>> disciplineXBooks = new HashMap<>();
private static List<Discipline> disciplines;

static {
Discipline d1 = new Discipline("D1", "discipline 1");
Discipline d2 = new Discipline("D2", "discipline 2");
Discipline d3 = new Discipline("D3", "discipline 3");
Discipline d4 = new Discipline("D4", "discipline 4");
disciplines = Arrays.asList(d1, d2, d3, d4);

disciplineXPreReqDisciplines.put (d3, Arrays.asList (d1, d2));
disciplineXPreReqDisciplines.put (d4, Arrays.asList (d2));

professorXDisciplines.put (new Professor ("professor a"), Arrays.asList (d1, d2));
professorXDisciplines.put (new Professor ("professor b"), Arrays.asList (d3));
professorXDisciplines.put (new Professor ("professor cv"), Arrays.asList (d1, d3, d4));

disciplineXBooks.put (d1, Arrays.asList ("book x", "book y"));
disciplineXBooks.put (d2, Arrays.asList ("book x", "book a", "book w"));
disciplineXBooks.put (d3, Arrays.asList ("book x", "book b"));
disciplineXBooks.put (d4, Arrays.asList ("book z"));
}

public List<Discipline> getPreRequisiteDisciplines (Discipline discipline) {
return disciplineXPreReqDisciplines.get (discipline);
}

public List<Discipline> getDisciplinesByProfessor(Professor professor) {
return professorXDisciplines.get (professor);
}

public List<String> getBooksByDiscipline(Discipline discipline) {
return disciplineXBooks.get (discipline);
}

public List<Professor> getProfessorByDiscipline (Discipline discipline) {
return professorXDisciplines.keySet()
.stream()
.filter (p->professorXDisciplines.get(p).contains(discipline))
//.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
.collect(Collectors.toList());
}

public Discipline getDisciplineByCode (String code) {
return disciplines
.stream()
.filter(s->s.getCode().equals(code))
.findAny()
.get();
}
}

Now, you will make the StudentDAO class:

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class StudentDAO {

public static enum FINANCIAL_STATUS {
OK (true, "OK"), PENDING (false, "Payment pending"), DOC_PENDING (true, "Document pending");

private boolean status;
private String description;
public boolean isStatus() {
return status;
}
public String getDescription() {
return description;
}
FINANCIAL_STATUS (boolean status, String description){
this.status = status;
this.description = description;
}
}

public static enum ACADEMIC_STATUS {
APPROVED , FAILED;
}

private static List<Student> students;
private static Map<String, FINANCIAL_STATUS> studentStatusPayment = new HashMap<>();
private static Map<Student, List<String>> studentXCourseName = new HashMap<>();

static {
Student s1 = new Student ("20010001", "student 1");
Student s2 = new Student ("20010002", "student 2");
Student s3 = new Student ("20010003", "student 3");
Student s4 = new Student ("20010004", "student 4");
students = Arrays.asList(s1, s2, s3, s4);

studentStatusPayment.put ("20010001", FINANCIAL_STATUS.OK);
studentStatusPayment.put ("20010002", FINANCIAL_STATUS.OK);
studentStatusPayment.put ("20010003", FINANCIAL_STATUS.PENDING);
studentStatusPayment.put ("20010004", FINANCIAL_STATUS.OK);

studentXCourseName.put (s1, Arrays.asList ("C01", "C02"));
studentXCourseName.put (s2, Arrays.asList ("C03"));
studentXCourseName.put (s3, Arrays.asList ("C04"));
studentXCourseName.put (s4, Arrays.asList ("C03", "C04"));
}

public static Map<String, FINANCIAL_STATUS> getStudentStatusPayment() {
return studentStatusPayment;
}
public List<Student> getEnrolledStudentByCourse(Course course) {
return studentXCourseName.keySet()
.stream()
.filter(s->studentXCourseName.get(s).contains(course.getCode()))
.collect(Collectors.toList());
}
public Student getStudentByEnrollment (String enrollment) {
return students
.stream()
.filter(s->s.getEnrollment().equals(enrollment))
.findAny()
.get();
}
}

Let's look at the ProfessorDAO class:

import java.time.LocalDate;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
public class ProfessorDAO {
private static Set<Professor> professors;
static {
Professor p1 = new Professor ("professor a", LocalDate.of (2001, 03, 22)),
p2 = new Professor ("professor b", LocalDate.of (1994, 07, 05)),
p3 = new Professor ("professor c", LocalDate.of (1985, 10, 12)),
p4 = new Professor ("professor cv", LocalDate.of (2005, 07, 17));

professors = Arrays
.stream (new Professor[]{p1, p2, p3, p4})
.collect (Collectors.toSet());

}
public Professor findByName (String name) {
return professors
.stream()
.filter(p->p.getName().equals(name))
.findAny()
.get();
}
}

We put a lot of responsibility in DisciplineDAO for simplicity. We could have increased the scope of the CourseDAO or ProfessorDAO class for access to data related to the Professor entity. 

Now, the following classes are the two Session Fa?ade implementations: AcademicFacadeImpl and FinancialFacadeImpl. It is important to note that this is only one of several ways to build this kind of application. The next part of this chapter will cover the business-object pattern, and here we will create a business object that centralizes the application's business rules instead of the Session Fa?ade:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Set;
import javax.ejb.Asynchronous;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
/**
* Session Bean implementation class AcademicFacadeImpl
*/
@Stateless
@LocalBean
public class AcademicFacadeImpl {

@Inject
private CourseDAO courseDAO;
@Inject
private DisciplineDAO disciplineDAO;
@Inject
private StudentDAO studentDAO;
@Inject
private ProfessorDAO professorDAO;

public List<Discipline> getDisciplinesByCourse(Course course) {
return courseDAO.getDisciplinesByCourse (course);
}
public List<Discipline> getPreRequisiteDisciplines (Discipline discipline) {
return disciplineDAO.getPreRequisiteDisciplines(discipline);
}
public List<Discipline> getDisciplinesByProfessor(Professor professor) {
return disciplineDAO.getDisciplinesByProfessor(professor);
}
public List<String> getBooksByDiscipline(Discipline discipline) {
return disciplineDAO.getBooksByDiscipline(discipline);
}
public List<Student> getEnrolledStudentByCourse(Course course) {
return studentDAO.getEnrolledStudentByCourse (course);
}
public void requestTestReview (Student student, Discipline discipline, LocalDate testDate) {
// TODO
}

private LocalDateTime scheduleTestReview (TestRevisionTO testRevisionTO)
{
LocalDateTime dateTime = null;
try {
Thread.sleep(10000);
// get some code to calculate the schedule date for the test review
Thread.sleep (5000); // simulate some delay during calculation
dateTime = LocalDateTime.now().plusDays(10);
if (dateTime.getDayOfWeek().equals(DayOfWeek.SUNDAY)) {
dateTime = dateTime.plusDays(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return dateTime;
}
private void sendEmail (TestRevisionTO testRevisionTO, LocalDateTime dateTime) {
Student student = studentDAO.getStudentByEnrollment (testRevisionTO.getEnrollment());
String enrollment = student.getEnrollment();
String studentName = student.getName();
String email = student.getEmail();
Discipline discipline = disciplineDAO.getDisciplineByCode (testRevisionTO.getDisciplineCode());
String disciplineName = discipline.getName();
String disciplineCode = discipline.getCode(); // testRevisionTO.getDisciplineCode()
String date = dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE);
String time = dateTime.format(DateTimeFormatter.ofPattern("HH:mm"));
// sending an email using the above information ...
System.out.println("sending an email to : " + studentName + " ...");
}

public void requestTestReview (@ObservesAsync TestRevisionTO testRevisionTO) {
System.out.println("matricula " + testRevisionTO.getEnrollment());
LocalDateTime dateTime = scheduleTestReview (testRevisionTO);
sendEmail (testRevisionTO, dateTime); // send an email with the schedule date for the test review:
}

public List<Professor> getProfessorsByDiscipline(Discipline discipline) {
return disciplineDAO.getProfessorByDiscipline(discipline);
}

public boolean canProfessorTeachDiscipline (Professor professor, Discipline discipline) {
return disciplineDAO.getDisciplinesByProfessor(professor) .contains(discipline);
}
}

Now, let's look at the FinancialFacadeImpl class:

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.inject.Inject;

/**
* Session Bean implementation class FinancialFacadeImpl
*/
@Stateless
@LocalBean
public class FinancialFacadeImpl {

@Inject
private StudentDAO studentDAO;

public FinancialFacadeImpl() {
}

public boolean canStudentEnroll (Student student) {
return studentDAO.getStudentStatusPayment().get (student.getEnrollment()).isStatus();
}

public boolean isStudentPending (Student student) {
FINANCIAL_STATUS status = studentDAO.getStudentStatusPayment().get (student.getEnrollment());
return (status.equals (FINANCIAL_STATUS.PENDING)) || (status.equals (FINANCIAL_STATUS.DOC_PENDING));
}
}

We can observe the @LocalBean annotation in the EJB Session Fa?ade. This means that the bean has a no-interface view. This is just a simplification because there is no need to apply local or remote interfaces for the explanation of the Session Fa?ade. Just remember, the requirement for local interfaces has been dropped since EJB 3.1.

The AcademicFacadeImpl Session Fa?ade has an asynchronous method with an event listener parameter. This method is responsible for providing the date and time for a test revision when requested by a student. For example:

  public void requestTestReview (@ObservesAsync TestRevisionTO testRevisionTO) {
System.out.println("Enrollment : " + testRevisionTO.getEnrollment());
LocalDateTime dateTime = scheduleTestReview (testRevisionTO);
sendEmail (testRevisionTO, dateTime); // send an email with the schedule date for the test review:
}

This event can be fired from a fa?ade client, typically a delegate or a web component (a servlet or a JSF-managed bean, for example). The event is injected into the client and fired according to the request. It is then fired together with a TestRevisionTO object:

// Event Injection :
@Inject
Event<TestRevisionTO> event;
...
...

// get the schedule date for a test revision:
TestRevisionTO testRevisionTO = new TestRevisionTO();
testRevisionTO.setEnrollment ("20010003");
testRevisionTO.setDisciplineCode("D3");
LocalDate date = LocalDate.of(2017, 11, 21);
LocalTime time = LocalTime.of (8, 30);
LocalDateTime dateTime = LocalDateTime.of(date, time);
testRevisionTO.setTestDateTime (dateTime);
event.fire (testRevisionTO);

The TestRevisionTO class is fired as follows:

import java.io.Serializable;
import java.time.LocalDateTime;

public class TestRevisionTO implements Serializable {

private String enrollment;
private String disciplineCode;
private LocalDateTime testDateTime;
public String getEnrollment() {
return enrollment;
}
public void setEnrollment(String enrollment) {
this.enrollment = enrollment;
}
public String getDisciplineCode() {
return disciplineCode;
}
public void setDisciplineCode(String disciplineCode) {
this.disciplineCode = disciplineCode;
}
public LocalDateTime getTestDateTime() {
return testDateTime;
}
public void setTestDateTime(LocalDateTime testDateTime) {
this.testDateTime = testDateTime;
}
}