CAPÍTULO 3. VALIDACIÓN DE LA SOLUCIÓN PROPUESTA
3.3. Modelo de Entrevista utilizada para la obtención de información
To create the Golfer entity, navigate to the Seam distribution directory and run the seam new-entity command using the following responses:
Entity class name: Golfer Master page name: golferList Detail page name: golfer
The new-entity command generates the Golfer JPA entity class containing a base set of properties, a page to list the golfers (golferList.xhtml) and corresponding page controller (GolferList), and a page to display the details of a golfer (golfer.xhtml) and corresponding page controller (GolferHome). The action beans components that support the CRUD operations are covered in depth in chapter 10. For now, let’s focus on using the Golfer entity class for the registration page.
The @Entity annotation added to the class declaration marks this class a JPA entity and the @Table annotation customizes the database table mapping. Whenever you add a new entity to the application, you also need to add a corresponding table to the database. Fortunately, Hibernate takes care of this task for you when the application is deployed as long as the value of the Hibernate property hibernate.hbm2ddl.auto in the resources/META-INF/persistence-dev-war.xml descriptor is update. Note that this is a change from the default value of validate set by seam-gen. Hibernate will also add additional table columns for any new entity properties that it detects.
I’ve decided to enhance the Golfer class, shown in listing 4.1, by making it a sub-class of Member, shown in listing 4.2. The use of entity inheritance sets the stage for a more flexible and realistic application. However, don’t concern yourself too much with the JPA annotations, such as @PrimaryKeyJoinColumn, if they aren’t familiar to you, because the primary focus here is on using this class as a form “backing” bean in a JSF page. In order for that to happen, it needs to be declared as a Seam component.
Table 4.7 The @Scope annotation Name: Scope
Purpose: Overrides the default scope for a Seam component Target: TYPE (class)
Attribute Type Function
value ScopeType The Seam context in which instances of this component are stored.
Table 4.6 lists the default value according to component type.
Default: none (required).
To make Golfer a Seam component, you simply add the @Name and @Scope annota-tions alongside the JPA annotations, shown in bold in listing 4.1. The component name newGolfer has been chosen since the component will be called on to instantiate a fresh instance of a golfer for use in the registration form. The @Scope annotation is present to explicitly bind the component to the event scope for demonstration, over-riding the default scope assignment for entity classes, which is the conversation scope.
Several bean properties have been added to support the use case, which map to col-umns in the GOLFER table. Also note the use of the Hibernate Validator annotations which, as you learned in the previous chapter, help enforce validations in the UI.
An alternative to adding @Name and @Scope to a JPA entity class is to declare the component in the Seam component descriptor using XML, which you’ll learn about in the next chapter. For now, appreciate that the use of annotations keeps things simple by eliminating XML configura-tion. Given that annotations are merely class metadata, they don’t affect the execution of the code (unless consulted using reflection). I confess that I prefer to limit the use of the @Name annotation to action beans and business components. Entity classes are the most frequently shared com-ponents, so conflicts can occur between teams over how to define the Seam annotations. Besides, entity classes instantiated by the persistence manager aren’t decorated with Seam interceptors. The primary use of a Seam entity component is to serve as a prototype—a new, transient (not yet persisted) instance. The prototype typically requires additional con-figuration that can only be defined in the component descriptor.
package org.open18.model;
public class Golfer extends Member { private String firstName;
private String lastName;
private Gender gender;
private Date dateJoined;
private Date dateOfBirth;
private String location;
@Column(name = "last_name", nullable = false) @NotNull @Length(max = 40)
public String getLastName() { return lastName; } public void setLastName(String lastName) { Listing 4.1 The Golfer entity class as a Seam component AUTHOR
NOTE
this.lastName = lastName;
}
@Column(name = "first_name", nullable = false) @NotNull @Length(max = 40)
public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName;
}
@Transient
public String getName() { return firstName + ' ' + lastName; } @Enumerated(EnumType.STRING)
public Gender getGender() { return gender; } public void setGender(Gender gender) { this.gender = gender;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "joined", nullable = false, updatable = false) @NotNull
public Date getDateJoined() { return dateJoined; } public void setDateJoined(Date dateJoined) { this.dateJoined = dateJoined;
}
@Temporal(TemporalType.DATE) @Column(name = "dob")
public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth;
}
public String getLocation() { return this.location; } public void setLocation(String location) {
this.location = location;
} }
Member is an abstract entity class that holds the username, passwordHash, and emailAddress inherited by the Golfer entity. The Member entity, shown in listing 4.2, uses a joined-inheritance strategy. This design makes it possible to have different types of members that are represented in separate tables. For the purpose of this registra-tion example, we assume that a golfer is the only type of member. Again, don’t get bogged down in this design if you’re new to JPA. Appreciate that the goal here is to establish a JavaBean that can be used to capture data from the registration form.
package org.open18.model;
Listing 4.2 The Member entity class, a superclass for application user types
@Table(name = "MEMBER", uniqueConstraints = { @UniqueConstraint(columnNames = "username"), @UniqueConstraint(columnNames = "email_address") })
public abstract class Member implements Serializable { private Long id;
private String username;
private String passwordHash;
private String emailAddress;
@Id @GeneratedValue
public Long getId() { return id; } public void setId(Long id) { this.id = id;
}
@Column(name = "username", nullable = false) @NotNull @Length(min = 6)
public String getUsername() { return username; } public void setUsername(String username) { this.username = username;
}
@Column(name = "password_hash", nullable = false) @NotNull
public String getPasswordHash() { return passwordHash; } public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash;
}
@Column(name = "email_address", nullable = false) @NotNull @Email
public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress;
} }
The registration form needs to capture a plain-text password from the user as well as a password confirmation. Corresponding properties aren’t found on either the Golfer or the Member entity since these fields aren’t to be persisted. Rather than dirtying the entity classes with transient fields, we’ll put these fields on a reusable JavaBean, PasswordBean, defined in listing 4.3. The PasswordBean also contains a business method for verifying that the two passwords entered are equivalent. This class is created under the src/model directory of the seam-gen project along with the entity classes.
package org.open18.auth;
import org.jboss.seam.annotations.Name;
@Name("passwordBean") public class PasswordBean { private String password;
private String confirm;
public String getPassword() { return password; }
Listing 4.3 A Seam JavaBean component that holds and verifies a new password
public void setPassword(String password) { this.password = password; } public String getConfirm() { return confirm; }
public void setConfirm(String confirm) { this.confirm = confirm; } public boolean verify() {
return confirm != null && confirm.equals(password);
} }
To give you a true appreciation of how easy Seam is making your life, I want to now show you how @Name and @Scope provide everything necessary to design a JSF form and action bean component to process the form submission. No XML is required.