-
1. Re: Data separation in multi tenant applications with seam
davidintx May 25, 2007 10:49 AM (in response to christian_zeidler)Regarding option #1--you probably don't want to simply include a "where client id = ?" in all your queries--what if you forget in once place, and expose data improperly?
Instead, look at the restricted entity manager in the wiki example, which applies a filter to all queries that use it. The configuration of the restricted entity manger is in components.xml, as follows:
<core:managed-persistence-context name="restrictedEntityManager"
auto-create="true"
entity-manager-factory="#{wikiEntityManagerFactory}">
<core:filters>#{accessLevelFilter}</core:filters>
</core:managed-persistence-context>
Setting up a filter is described starting on page 540 of the Java Persistence with Hibernate book--I highly recommend it. -
2. Re: Data separation in multi tenant applications with seam
javabr May 25, 2007 3:01 PM (in response to christian_zeidler)I am using this hibernate filters in my application too.. I can not say how easy it is.
I have used this strategy:
1. My users has a Company_id column.
2. When they login on my application I set this Filter parameter (company_id) fro all my relevant entities.. done... from now, each single query, collection, etc.. is automaticaly added with a " and company_id = ? " expression on where clause..
I got some problems configuring it because there is some tricks defining the filter parameter value... but after solve that, my world becomes even easier. :) -
3. Re: Data separation in multi tenant applications with seam
gavin.king May 28, 2007 2:38 PM (in response to christian_zeidler)#1 Can be implemented using a Hibernate Filter, which we have special support for in Seam.
#2 or #3 Can be implemented using multiple SessionFactories, or EntityManagerFactories. You would need to do a little trick when the user logs in, to map the SessionFactory you are interested in for this user to the context variable that your Seam-managed persistence context uses. But this is trivial to implement and will work well. -
4. Re: Data separation in multi tenant applications with seam
gavin.king May 28, 2007 2:39 PM (in response to christian_zeidler)Concerning option 1, storing the client ID in the session context and including it in all the queries should do the job.
If you don't want to include it in all queries, use Hibernate filters instead. -
5. Re: Data separation in multi tenant applications with seam
gavin.king May 28, 2007 2:42 PM (in response to christian_zeidler)The "trick" looks like this, by the way:
Contexts.getSessionContext().set("entityManagerFactory", Contexts.getApplicationContext().get( user.getCompanyName() + "EntityManagerFactory" ) );
-
6. Re: Data separation in multi tenant applications with seam
christian_zeidler May 31, 2007 7:49 AM (in response to christian_zeidler)Hi, Thanks for your input, it's been realy helpfull!
I'm currently prototyping a solution with hibernate filters. http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4045495 has been helping me with getting the information together from the many documentation sources :-) (hibernate core, annotations, entityManager, seam, etc.)
The problem I'm facing now is that i've got two entityManagers configured in components.xml, one of them uses the filters. Befor a user's login, of cource I need the standard entityManager. During authentification, I can set the parameter for the filter -> But then... How do I set the FilteredEntityManager as the default for this session?
The solution that is provided in the post that I linked above consists in overriding the PersistenceContextName for every entityList Class. I don't think that this is an optimal solution. I would prefer to set the percistenceContext name in the authentification method once and for all right after setting the filter parameters... any hints on how this can be done?
Thank you! -
7. Re: Data separation in multi tenant applications with seam
gavin.king May 31, 2007 12:33 PM (in response to christian_zeidler)An @Factory method for "entityManager" seems like a reasonable approach. When the user is logged in, it would return the filtered one, otherwise the unfiltered one.
-
8. Re: Data separation in multi tenant applications with seam
christian_zeidler May 31, 2007 1:03 PM (in response to christian_zeidler)please help me, where would that method go?
-
9. Re: Data separation in multi tenant applications with seam
gavin.king May 31, 2007 1:12 PM (in response to christian_zeidler)Its a factory method, so it can go anywhere you like. (ie. on any Seam component.)
-
10. Re: Data separation in multi tenant applications with seam
christian_zeidler May 31, 2007 3:07 PM (in response to christian_zeidler)OK, so what I have dome is to create two managed-persistence-contexts in components.xml
<core:managed-persistence-context name="standardEntityManager" auto-create="true" persistence-unit-jndi-name="java:/standardEntityManagerFactory"> </core:managed-persistence-context> <core:filter name="clientFilter"> <core:name>clientFilter</core:name> <core:parameters> <key>clientId</key> <value>#{clientId}</value> </core:parameters> </core:filter> <core:managed-persistence-context name="filterEntityManager" auto-create="true" persistence-unit-jndi-name="java:/filteredEntityManagerFactory"> <core:filters><value>#{clientFilter}</value></core:filters> </core:managed-persistence-context>
Then I added a @Factory Method for entityManager to Authenticator.java@In EntityManager entityManager; @Factory("entityManager") public EntityManager selectEntityManager(){ try{ Context jndi = new InitialContext(); if(identity.isLoggedIn()){ EntityManagerFactory emf = (EntityManagerFactory) jndi.lookup("java:/filteredEntityManagerFactory"); log.info("just set: " + "java:/filteredEntityManagerFactory"); return emf.createEntityManager(); }else{ EntityManagerFactory emf = (EntityManagerFactory) jndi.lookup("java:/standardEntityManagerFactory"); log.info("just set: " + "java:/standardEntityManagerFactory"); return emf.createEntityManager(); } }catch(NamingException e){ log.info("error " + e.toString()); return null; } }
Now when I run this and try to login, I get an exception:20:55:47,143 ERROR [SeamLoginModule] Error invoking login method javax.faces.el.EvaluationException: Exception while invoking expression #{authenticator.authenticate} at org.apache.myfaces.el.MethodBindingImpl.invoke(MethodBindingImpl.java:153) at org.jboss.seam.actionparam.ActionParamBindingHelper.invokeTheExpression(ActionParamBindingHelper.java:58) at org.jboss.seam.actionparam.ActionParamMethodBinding.invoke(ActionParamMethodBinding.java:75) at org.jboss.seam.core.Expressions$2.invoke(Expressions.java:148) at org.jboss.seam.security.jaas.SeamLoginModule.login(SeamLoginModule.java:104) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at javax.security.auth.login.LoginContext.invoke(LoginContext.java:769) at javax.security.auth.login.LoginContext.access$000(LoginContext.java:186) at javax.security.auth.login.LoginContext$5.run(LoginContext.java:706) at java.security.AccessController.doPrivileged(Native Method) at javax.security.auth.login.LoginContext.invokeCreatorPriv(LoginContext.java:703) at javax.security.auth.login.LoginContext.login(LoginContext.java:575) at org.jboss.seam.security.Identity.authenticate(Identity.java:247) at org.jboss.seam.security.Identity.authenticate(Identity.java:240) at org.jboss.seam.security.Identity.login(Identity.java:170) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at com.sun.el.parser.AstValue.invoke(AstValue.java:174) at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:286) at com.sun.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:68) at com.sun.facelets.el.LegacyMethodBinding.invoke(LegacyMethodBinding.java:69) at org.apache.myfaces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:63) at javax.faces.component.UICommand.broadcast(UICommand.java:106) at org.ajax4jsf.framework.ajax.AjaxViewRoot.processEvents(AjaxViewRoot.java:180) at org.ajax4jsf.framework.ajax.AjaxViewRoot.broadcastEvents(AjaxViewRoot.java:158) at org.ajax4jsf.framework.ajax.AjaxViewRoot.processApplication(AjaxViewRoot.java:329) at org.apache.myfaces.lifecycle.LifecycleImpl.invokeApplication(LifecycleImpl.java:343) at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:86) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:137) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:252) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.jboss.seam.web.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:63) at org.jboss.seam.debug.hot.HotDeployFilter.doFilter(HotDeployFilter.java:60) at org.jboss.seam.web.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:49) at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45) at org.jboss.seam.web.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:49) at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:57) at org.jboss.seam.web.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:49) at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:79) at org.jboss.seam.web.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:49) at org.jboss.seam.web.SeamFilter.doFilter(SeamFilter.java:84) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.ajax4jsf.framework.ajax.xmlfilter.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:96) at org.ajax4jsf.framework.ajax.xmlfilter.BaseFilter.doFilter(BaseFilter.java:220) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:202) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:173) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:213) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:178) at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:175) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:432) at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:74) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:126) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105) at org.jboss.web.tomcat.tc5.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:156) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:107) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148) at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869) at org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664) at org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527) at org.apache.tomcat.util.net.MasterSlaveWorkerThread.run(MasterSlaveWorkerThread.java:112) at java.lang.Thread.run(Thread.java:595) Caused by: java.lang.NullPointerException at com.skyline.Authenticator.authenticate(Authenticator.java:62) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.jboss.seam.util.Reflections.invoke(Reflections.java:20) at org.jboss.seam.intercept.RootInvocationContext.proceed(RootInvocationContext.java:31) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:57) at org.jboss.seam.interceptors.RollbackInterceptor.aroundInvoke(RollbackInterceptor.java:34) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:69) at org.jboss.seam.interceptors.BijectionInterceptor.aroundInvoke(BijectionInterceptor.java:47) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:69) at org.jboss.seam.interceptors.MethodContextInterceptor.aroundInvoke(MethodContextInterceptor.java:27) at org.jboss.seam.intercept.SeamInvocationContext.proceed(SeamInvocationContext.java:69) at org.jboss.seam.intercept.RootInterceptor.invoke(RootInterceptor.java:103) at org.jboss.seam.intercept.JavaBeanInterceptor.interceptInvocation(JavaBeanInterceptor.java:151) at org.jboss.seam.intercept.JavaBeanInterceptor.invoke(JavaBeanInterceptor.java:87) at com.skyline.Authenticator_$$_javassist_101.authenticate(Authenticator_$$_javassist_101.java) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:585) at org.apache.myfaces.el.MethodBindingImpl.invoke(MethodBindingImpl.java:129) ... 69 more
The nullpointerexception happens at lineUsers user = (Users) entityManager.createQuery( "from Users where user_name = :username and password = :password") .setParameter("username", Identity.instance().getUsername()) .setParameter("password", Identity.instance().getPassword()) .getSingleResult();
That's where the entityManager is first used... it doesn't seem to get innitiated in the @Factory method... -
11. Re: Data separation in multi tenant applications with seam
christian_zeidler May 31, 2007 3:30 PM (in response to christian_zeidler)Things are getting better...
by adding (create=true) to@In(create=true) EntityManager entityManager;
and adding a second persistence-unit to persistence.xml with<property name="jboss.entity.manager.factory.jndi.name" value="java:/filteredEntityManagerFactory"/>
I am runnig free of exception. The only problem is that the filter is now not working. All users can see all the data... -
12. Re: Data separation in multi tenant applications with seam
gavin.king May 31, 2007 5:17 PM (in response to christian_zeidler)Nonono!
@In EntityManager filteredEntityManager; @In EntityManager unFilteredEntityManager; @Factory(value="entityManager", autoCreate=true) public EntityManager selectEntityManager(){ return identity.isLoggedIn() ? filteredEntityManager : unFilteredEntityManager; }
-
13. Re: Data separation in multi tenant applications with seam
gavin.king May 31, 2007 5:18 PM (in response to christian_zeidler)Where filteredEntityManager and unFilteredEntityManager are both seam-managed PCs defined in components.xml.
-
14. Re: Data separation in multi tenant applications with seam
gavin.king May 31, 2007 5:19 PM (in response to christian_zeidler)Note the use of autoCreate=true.