1 2 Previous Next 20 Replies Latest reply on Jun 29, 2007 8:52 PM by Gavin King

    Data separation in multi tenant applications with seam

    Christian Zeidler Newbie

      We are currently designing an application that will be exposed to multiple customers, and are therefore evaluating seam. The question we are currently discussing is how to separate the customers? data. Generally I would consider three alternatives:

      1) Sharing the same database and schema. Associating every record with one client by using an ID.
      2) Sharing the same database, but using separate schemas. This would mean that the tables in the schema that hold client specific data would be created for each client and identified via the table name (e.g. client1_data, client2_data etc.).
      3) Using separate databases. And let the application connect to the correct database on logon based on the user?s information.

      Each of these options does have advantages and disadvantages concerning scalability, performance and security related issues. While we are still evaluating these options, I?m interested in how these options could be implemented in a seam application.

      Concerning option 1, storing the client ID in the session context and including it in all the queries should do the job.

      Are there any good strategies concerning option 2 or 3?

      I?d like to call upon the community?s experience with these topics, and ask you for your opinions on these strategies and their implementation within seam.

      Thanks? for your help!
      Christian

        • 1. Re: Data separation in multi tenant applications with seam
          davidintx Newbie

          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
            Leo Newbie

            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 Master

              #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 Master

                 

                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 Master

                  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 Newbie

                    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 Master

                      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 Newbie

                        please help me, where would that method go?

                        • 9. Re: Data separation in multi tenant applications with seam
                          Gavin King Master

                          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 Newbie

                            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 line
                            Users 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 Newbie

                              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 Master

                                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 Master

                                  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 Master

                                    Note the use of autoCreate=true.

                                    1 2 Previous Next