Reputation: 205
This is a fairly lengthy (not overly complex) design question so please bear with me. I'm trying to implement a person/role management system with POJOs and JPA. I'm fairly new to ORM and this is mostly a mapping problem.
I've got this working as POJOs and am comfortable with the caller-level API, but would now like to map it to a database using JPA or Hibernate in a Seam environment.
My implementation is based on the Decorator (GoF) and Person Role Object patterns (Baumer/Riehle et al). All roles are hard-coded and run-time addition of new roles is not supported since it would require code changes to extend behavior. I'd use user groups to implement security and permissions.
There is a Person interface with role management methods such as addRole(), removeRole(), hasRole(), getRole(), getRoles(), among other things. The concrete implementation is provided by a PersonImpl class.
There is an abstract class Role which also implements the Person interface (for decorator substitution equivalence), and a RoleImpl class that extends it. The Role class holds a reference to a person instance, using it to service any method / property calls on the person interface, meaning all subclasses of Role can handle the Person interface. The role constructor takes the person object as a parameter.
These are the interfaces/classes:
public interface Person {
public String getFirstName();
public void setFirstName(String firstName);
.
.
.
public boolean isEnabled();
public void setEnabled(boolean enabled);
public Set<Role> getRoles();
public Role addRole(Class<? extends Role> roleType);
public void removeRole(Class<? extends Role> roleType);
public boolean hasRole(Class<? extends Role> roleType);
public Role getRole(Class<? extends Role> roleType);
public enum Gender {MALE, FEMALE, UNKNOWN};
}
public class PersonImpl implements Person {
.
.
.
}
public abstract class Role implements Person {
protected PersonImpl person;
@Transient
protected abstract String getRoleName();
protected Role() {}
public Role(PersonImpl person) {
this.person = person;
}
public String getFirstName() {
return person.getFirstName();
}
public void setFirstName(String firstName) {
person.setFirstName(firstName);
}
public Set<Role> getRoles() {
return person.getRoles();
}
public Role addRole(Class<? extends Role> roleType) {
return person.addRole(roleType);
}
.
.
.
}
public abstract class RoleImpl extends Role {
private String roleName;
protected RoleImpl() {}
public RoleImpl(PersonImpl person) {
super(person);
}
.
.
.
}
public class Employee extends RoleImpl {
private Date joiningDate;
private Date leavingDate;
private double salary;
public Employee(PersonImpl person) {
super(person);
}
.
.
.
}
This diagram shows the class relationships:
(If you can't see the diagram inline, view it here via yUML)
I'd use these classes like so:
Person p = new Person("Doe", "John", Person.MALE, ...);
// assuming Employee extends Role
Employee e = (Employee)p.addRole(Employee.class);
Since the Role class also implements the Person interface, I can also do:
// assuming Parent extends Role
Parent parent = new Parent((PersonImpl)p);
e.addRole(Parent.class);
e.getDateOfBirth(); // handled by the decorated person class
// assuming Manager extends Employee extends Role
Manager m = (Manager)p.getRole(Manager);
if (m.hasRole(Employee.class) {
// true since Manager derives from Employee
}
The questions I have are:
(a) Is this implementation needlessly complex and if so what would be a simpler approach? Note that this goes into a non-trivial business application and not a kiddy project, and I think the role subclassing is important to leverage behavior in cases such as Employee, Manager, etc.
(b) How do I map this in JPA/Hibernate?
(c) Can it be mapped so I can also take advantage of Seam Identity Management? (My definition of Role is clearly not analogous to Seam's)
(d) If I go with a table-per-subclass (InheritanceType.JOINED) mapping strategy, I map PersonImpl as PERSONS and RoleImpl as ROLES tables, and map each subclass of RoleImpl (such as Employee and Parent) to their own tables as EMPLOYEES and PARENTS.
I would then have a @ManyToMany relationship between PERSONS and ROLES (on the roles collection in PersonImpl), with the join-table PERSON_ROLES.
Now the problem is that the EMPLOYEES and PARENTS tables, etc. have only the ROLE_ID reference, since the inheritance mapping strategy obviously considers them to be extensions to Roles (ROLES) whereas I need them as addendum to PERSON_ROLES, and require the USER_ID + ROLE_ID key to be resolved properly, or at least the USER_ID.
I would prefer a normalized database with the attendant reliance on extra joins than a denormalized database which will be difficult to maintain and likely bunch up a ton of unused and irrelevant fields, which is why I think table-per-subclass is the way to go.
Or is a table-per-class-hierarchy (InheritanceType.SINGLE_TABLE) with a discriminator column OK (from database maintenance perspective) in this scenario? Note that some roles are likely to have dozens of properties/fields.
(e) Is there a better alternative to this design?
Would very much appreciate any ideas/suggestions.
Upvotes: 8
Views: 6594
Reputation: 33785
As said
So please bear with me
So my answer will be based on what you said
All roles are hard-coded... There is an abstract class Role...
If There is an AbstractRole class, so i suppose some properties defined in AbstractRole class are inherited by all of subclasses
@Entity
/**
* Which strategy should we use ??? keep reading
*/
@Inheritance
public abstract class AbstractRole implements Serializable {
private Integer id;
private String commonProperty;
private String otherCommonProperty;
private String anotherCommonProperty;
@Id
@GeneratedValue
public Integer getId() {
return this.id;
}
// getter's and setter's
}
Now Employee (Same approach used by Parent and Student)
@Entity
public class Employee extends AbstractRole {
private String specificEmployeeProperty;
// getter's and setter's
}
But which inheritance strategy To use ???
InheritanceType.JOINED
InheritanceType.SINGLE_TABLE
Now let's see
There is a Person with role management methods such as addRole(), removeRole(), hasRole(), getRole(), getRoles(), among other things
Well, if i see something like addRole method, i suppose Person has Either @OneToMany or @ManyToMany relationship with AbstractRole class. I know, i know... You have @ManyToMany relationship but i really advice you split your @ManyToMany into @OneToMany - @ManyToOne relationship. See here how To
@Entity
public class Person implements Serializable {
private Integer id;
private List<AbstractRole> roleList;
@Id
@GeneratedValue
public Integer getId() {
return this.id;
}
@OneToMany
public List<AbstractRole> getRoleList() {
return this.roleList;
}
// getter's and setter's
}
And finally
Can it be mapped so I can also take advantage of Seam Identity Management
Seam Identity Management allows you implement your own behavior regardless of using JPA or not.
@Name("authenticationManager")
public class AuthenticationManager {
/**
* Starting with Seam 2.1+, you should use Credentials instead of Identity
* To collect your username and password
*
* Your JSF Form should looks like
*
* <h:inputText value="#{credentials.username}"/>
* <h:inputSecret value="#{credentials.password}"/>
*/
private @In org.jboss.seam.security.Credentials credentials;
/**
* Login method
* must take no arguments
* must return a boolean indicating whether the credentials could be verified
*
* method name does not mind
*/
public boolean authenticate() {
/**
* Logic To verify whether credentials is valid goes here
*/
}
}
And define your authenticate-method in /WEB-INF/components.xml
<security:identity authenticate-method="#{authenticationManager.authenticate}"/>
About your mapping It depends on business requirement, customer needs and so on... but I Think it can be simpler as shown above
Good lucky!
Upvotes: 6