Skip to content

Using a custom implementation of ApplicationUser

Dan Haywood edited this page Feb 8, 2015 · 3 revisions

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;
}