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.
Hibernate supports the three basic inheritance mapping strategies: table per class hierarchy. table per subclass. table per concrete class.
Entity inheritance means that we can use polymorphic queries for retrieving all the subclass entities when querying for a superclass. Since Hibernate is a JPA implementation, it contains all of the above as well as a few Hibernate-specific features related to inheritance.
JPA defines three inheritance strategies namely, SINGLE_TABLE , TABLE_PER_CLASS and JOINED . Single table inheritance is default, and table per class is optional so all JPA vendors may not support it.
JPA and Hibernate support 4 inheritance strategies which map the domain objects to different table structures.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With