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. > >
