Using a custom implementation of ApplicationUser
As per
this thread on the Isis mailing list, this wiki page explains two different means by which an ApplicationUser
could be extended to include additional information.
In the above thread the requirement was to include a reference to some other application-defined entity, namely an entity called Organization
. Basically we want to have somewhere to store the ApplicationUser
/Organization
tuple.
The recommended approach is to store the ApplicationUser
/Organization
tuple as a separate entity (let's call it OrganizationUser
) that is 1:1 with the ApplicationUser
, and then use contributed properties and actions to make it appear as if the Organization
is an actual property of ApplicationUser
.
We start with an Organization
entity:
public class Organization {
// name property etc
private String name;
public String getName() { ... }
public void setName(String name) { ... }
// 1:m bidir collection of OrganizationUser, hidden
@Persistent(mappedBy="organization")
private SortedSet<OrganizationUser> users;
@Hidden
public SortedSet<OrganizationUser> getUsers() { ... }
public void setUsers(SortedSet<OrganizationUser> users) { ... }
// 1:m derived collection of ApplicationUsers
@NotPersisted
@NotPersistent
public List<ApplicationUser> getApplicationUsers() {
return Lists.newArrayList(
Iterables.transform(getUsers(), OrganizationUser.AS_USER));
}
}
Next, we have our OrganizationUser
, the tuple between Organization
and ApplicationUser
:
public class OrganizationUser {
public static Function<OrganizationUser, ApplicationUser> AS_USER =
new Function<OrganizationUser, ApplicationUser>() {
public ApplicationUser apply(OrganizationUser ou) {
return ou.getApplicationUser();
}
};
// the org (other side of the 1:m bidir relationship, see above)
private Organization organization;
public Organization getOrganization() { ... }
public void setOrganization(Organization o) { ... }
// the corresponding appUser
private ApplicationUser applicationUser;
public ApplicationUser getApplicationUser() { ... }
public void setApplicationUser(ApplicationUser au) { ... }
}
Next, we have a service that will contribute:
-
'organization' as a property to applicationUser
-
'assignTo' as an action on applicationUser
that is:
@DomainService
public class OrganizationContributions {
@NotInServiceMenu
@ActionSemantics(As.SAFE)
@NotContributed(As.ACTION) // ie contributed as property
public Organization getOrganization(ApplicationUser appUser) {
OrganizationUser orgUser = organizationUsers.findFor(appUser);
return orgUser != null? orgUser.getOrganization(): null;
}
@NotInServiceMenu
public ApplicationUser assignTo(ApplicationUser appUser, Organization
organization) {
organizationUsers.create(organization, appUser);
return appUser;
}
@Inject
private OrganizationUsers organizationUsers;
}
This delegates to an OrganizationUsers
repo:
@DomainService
public class OrganizationUsers {
@Programmatic
public OrganizationUser findFor(ApplicationUser appUser) {
return firstMatch(OrganizationUser.class, "findByApplicationUser",
"applicationUser", appUser);
}
@Programmatic
public OrganizationUser create(Organization org, ApplicationUser
appUser) {
OrganizationUser ou = newTransientInstance(OrganizationUser.class);
ou.setOrganization(org);
ou.setApplicationUser(appUser);
persist(ou);
return ou;
}
}
The last bit of the puzzle is handling any deletions. The security module emit strongly typed events for all actions, so we can simply subscribe to this in order to keep OrganizationUser
in sync:
@DomainService
public class OrganizationSubscriptions {
@Programmatic
@PostConstruct
public void postConstruct() {
eventBusService.register(this);
}
@Subscribe
public void on(ApplicationUser.Deleted ev) {
if(ev.getPhase() == Phase.EXECUTING) {
ApplicationUser appUser = ev.getApplicationUser();
OrganizationUser orgUser = organizationUsers.findFor(appUser);
if(orgUser != null) {
// delete
removeIfNotAlready(orgUser);
}
}
}
@Inject
private EventBusService eventBusService;
}