Hi Jan-Willem,

Let me show you a possible implementation of the multi-tenancy feature some of 
us (Dan, Jeroen, Martin and me) implemented in a project.

We have an abstract base class for our Domain Entities, like this one:

public abstract class AbstractDomainEntity<T extends AbstractDomainEntity <T>>
        extends AbstractDomainObject implements Comparable<T>, 
WithApplicationTenancy {

    // //////////////////////////////////////
    // Application Tenancy.
    // //////////////////////////////////////

    protected static String TENANT_PATH_UNIVERSAL = "/";

    @Property(hidden = Where.EVERYWHERE)
    @Override
    public ApplicationTenancy getApplicationTenancy() {
        final ApplicationTenancy applicationTenancy = 
this.applicationTenancies.findTenancyByPath(this.tenancyPath());

        if (applicationTenancy == null) {
            throw new FatalException("Domain Object without Application Tenancy 
!!! That's forbidden.");
        }

        return applicationTenancy;
    }

    @Programmatic
    public abstract String tenancyPath();


    ….

}


What is relevant here is that we force any Domain Entity to override a 
“tenancyPath” method which is used for giving the tenancy path for any given 
domain object (ie, Domain Entity instance).
As it’s a method, it can be programmatically return a static path (ie, the same 
for all Domain Entity instances) or compute it dinamically (ie. from properties 
for this given concrete domain object).

For example, we have an “Account” Domain Entity, whose tenancy path is compute 
as here:

public class Account extends AbstractDomainEntity <Account> {


         ….

    @Override
    @Programmatic
    public String tenancyPath() {
        if (this.getApplicationUser() != null) {
            return "/accounts/" + this.getApplicationUser().getName();
        } else {
            return “/ONLY_APP_ADMINS”;
        }

      ….

}


Also, this entity is relevant because it allow us to explicitely model in our 
Domain the Isis Security add-on Users, having a 1-to-1 relationship with the 
corresponding add-on entity.

And we can also indirectly evaluate if a given Account has a specific Security 
Role to know about our security policies at the Domain level (only if needed; 
perhaps not best practice if we want to keep Security completely orthogonal to 
our Domain):


public class Account extends AbstractDomainEntity <Account> {

         ….

    // {{ ApplicationUser (property)
    private ApplicationUser applicationUser;

    // Acts as a Title because ApplicationUser has #title() impl
    @Column(allowsNull = "false")
    @PropertyLayout(hidden = Where.ANYWHERE)
    public ApplicationUser getApplicationUser() {
        return this.applicationUser;
    }

    public void setApplicationUser(final ApplicationUser applicationUser) {
        this.applicationUser = applicationUser;
    }

    // }}

    // {{ hasSecurityRole (action)
    @Action(semantics = SemanticsOf.SAFE, hidden = Where.EVERYWHERE)
    @MemberOrder(sequence = "1")
    public Boolean hasSecurityRole(@ParameterLayout(named = "Application Role") 
final ApplicationRole applicationRole) {
        if (this.getApplicationUser() != null) {
            return 
this.getApplicationUser().getRoles().contains(applicationRole);
        } else {
            return false;
        }
    }

    // }}

      ….
}


As an example of dinamically computing it, for the “Person” entity, we delegate 
the tenancy path to the Account it belongs to.
And if, for any reason, this concrete Domain Object does not have an associated 
Account, it will become visible only to SysAdmins.


public class Person extends AbstractDomainEntity <Person> {

         ….

    @Override
    @Programmatic
    public String tenancyPath() {
        if (this.getAssociatedAccount() != null) {
            return this.getAssociatedAccount().tenancyPath();
        } else {
            return “/ONLY_APP_ADMINS”;
        }
    }

      ….
}


For the “Order” Domain Entity, we use a different approach, mapping the order 
“owner” by email with the corresponding Account.


public class Order extends AbstractDomainEntity <Order> implements 
OrderForPricing {

      ….
    @Override
    @Programmatic
    public String tenancyPath() {
        if ((this.meService.me() != null) && 
this.meService.me().getEmail().equals(this.getEmail())) {
            return this.meService.me().tenancyPath();
        } else {
            return “/ONLY_APP_ADMINS”;
        }
    }
      ….
}


Being the “MeService” a service that “maps” the current Isis user to our Domain:


@DomainService
@DomainServiceLayout(menuBar = DomainServiceLayout.MenuBar.TERTIARY)
public class MeService extends AbstractMyDomainService {

      ….

    @Action(semantics = SemanticsOf.SAFE)
    @MemberOrder(sequence = "2")
    @ActionLayout(cssClass = "iconEditProfile")
    public Account me() {
        final String currentUserName = this.container.getUser().getName();
        return this.accounts.findByName(currentUserName);
    }
      ….
}


As you can see, the tenancy path can give you the desired level of simplicity 
or complexity for your Domain Entities.

You could have something simple, like for example, a “Tenant” entity that could 
be referenced in any other Domain Entities with a property, and composing the 
tenancy path simply with something like here (not tested in IDE:


public class Tenant extends AbstractDomainEntity < Tenant > {

    private String name;

    public void setName(final String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

      ….

}



public class MultiTenantEntity extends AbstractDomainEntity <Order> implements 
OrderForPricing {

    private Tenant tenant;

    public void setTenant(final Tenant tenant) {
        this.tenant = tenant;
    }

    public String getTenant() {
        return this.tenant;
    }

    public String tenancyPath() {
        if (this.getTenant() != null) {
            return “/“ + this.getTenant().getName();
        } else {
            return “/ONLY_APP_ADMINS”;
        }
    }
      ….
}
  


HTH,

Oscar



> El 18 jun 2016, a las 16:31, Jan-Willem Gmelig Meyling 
> <[email protected]> escribió:
> 
> Hi Dan,
> 
> Thanks for your response! I will create an issue in the Jira issue tracker 
> for this. Right now it’s not so important that I can allocate some time for 
> it now, but I may very well implement it later on. First I want too look 
> somewhat more into the framework and Wicket. Three other questions that I 
> currently have:
> 
> - Is there an example of how the tenancy should be used for the security 
> module? I am wondering whether it is possible to ensure SimpleObjects can 
> only be accessed by allowed users (/user groups). 
> 
> - Is there a way to provide a custom stylesheet from scratch, or can you only 
> make additions to the existing styles?
> 
> - Is there a way to use multiple persistence units? I know in CDI/JPA you can 
> inject another persistence unit with @PersistenceContext(“unit-name”). Is 
> something similar possible with DataNucleus / Isis?
> 
> Cheers,
> 
> Jan-Willem
> 
> 
> 
> 
>> On 16 Jun 2016, at 17:50, Dan Haywood <[email protected]> wrote:
>> 
>> Hi Jan-Willem
>> 
>> sorry not to reply on this question before now.
>> 
>> Short answer is that this isn't part of the Restful Objects spec, and
>> therefore isn't possible currently.  That said, I do think that the Naked
>> Objects .NET implementation has made some small extensions to the spec to
>> support this capability, but I don't know the exact details.
>> 
>> By all means raise a JIRA ticket on this (though I don't know exactly when
>> it would be implemented).  If you're really need the feature then I'll be
>> happy to provide some pointers as to how you might implement it and
>> contribute back.
>> 
>> Thanks
>> Dan
>> 
>> 
>> On 29 May 2016 at 12:55, Jan-Willem Gmelig Meyling <
>> [email protected]> wrote:
>> 
>>> 
>>> 
>>> Hey all,
>>> 
>>> I've been wondering how I could nicely implement a collection of Enums
>>> as input for an Action. The values do not come from an persisted entity,
>>> as it would be part of as I imagine it to be a request POJO.
>>> 
>>> I know that HTTP headers allow a path parameter to be defined multiple
>>> times. So an EnumSet is usually seen as "?foo=BAR&foo=BAZ". In Jackson I
>>> would use the Enum to String serialization and end up with something
>>> similar to : { "foo" : [ "BAR", "BAZ"] }
>>> 
>>> I currently transformed my options to booleans so that they become
>>> visible as checkboxes, but I was wondering how to do this properly.
>>> 
>>> Any response is very much appreciated,
>>> 
>>> Kind regards,
>>> 
>>> Jan-Willem



Óscar Bou Bou
Socio - IT & GRC Management Services Director
m: +34 620 267 520
s:  <http://www.govertis.com/>www.govertis.com <http://www.govertis.com/> e: 
[email protected] <mailto:[email protected]>

LinkedIn: https://www.linkedin.com/in/oscarbou 
<https://www.linkedin.com/in/oscarbou>
Twitter:        @oscarbou <https://twitter.com/oscarbou>



Este mensaje y los ficheros anexos son confidenciales. Los mismos contienen 
información reservada que no puede ser difundida. Si usted ha recibido este 
correo por error, tenga la amabilidad de eliminarlo de su sistema y avisar al 
remitente mediante reenvío a su dirección electrónica; no deberá copiar el 
mensaje ni divulgar su contenido a ninguna persona.

Su dirección de correo electrónico junto a sus datos personales constan en un 
fichero titularidad de GOVERTIS ADVISORY SERVICES, S.L. cuya finalidad es la de 
mantener el contacto con Ud. Si quiere saber de qué información disponemos de 
Ud., modificarla, y en su caso, cancelarla, puede hacerlo enviando un escrito 
al efecto, acompañado de una fotocopia de su D.N.I. a la siguiente dirección: 
GOVERTIS ADVISORY SERVICES, S.L. Avda Cortes Valencianas, 58 – 8º - 6ª. 46015 - 
Valencia,  y Paseo de la Castellana, 153, 28045 - MADRID. Asimismo, es su 
responsabilidad comprobar que este mensaje o sus archivos adjuntos no contengan 
virus informáticos, y en caso que los tuvieran eliminarlos.


Reply via email to