Hi Oscar,

Thanks for your very detailled answer. I will definitely play around with it in 
the next few days!

Kind regards,

Jan-Willem

> On 19 Jun 2016, at 22:09, Óscar Bou - GOVERTIS <[email protected]> wrote:
> 
> 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] <mailto:[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] 
>>> <mailto:[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] <mailto:[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
> 
> <govertis1.png>
> 
> Ó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