10 Replies Latest reply on Jun 25, 2006 6:41 PM by javalover75

    Using multiple persistence units/EntityManagerFactories with

    hennejg

      Hi,

      although I would assume that what I'm trying to achieve is a rather common problem, I am still struggling with how to solve it in EJB 3.

      I would like to be able to define separate database "realms" for an application which can be used in parallel. The application is based on EJB3 EntityBean POJOs. Here, I define "realm" as a place to which the data model is persisted, i.e. the same model should be persistable to different databases depending on the context of an access.
      For example, I'd like to be able to say
      - store all stuff in data source java:/some-mysql-database for the context "realm 1" and
      - store all stuff in data source java:/some-oracle-database for the context "realm 2"
      all within the same running JBoss instance.

      On the DAO side my approach is to
      - encode the context inside the principal of (authenticated) accesses (all accesses are necessarily authenticated)
      - intercept calls using standard EJB3 interceptors which provide injection of a context-dependant EntityManager on a per-call basis.

      Implementing the above approach requires the configuration of a separate persistence unit per context. What I'm currently lacking is a way to programmatically configure the persistence unit/EntityManagerFactory from a default "template" while overriding the data source.

      I found this forum post which doesn't sound very encouraging. If the data source cannot be overridden, configuration becomes considerably harder because of the necessity to mangle the persistence.xml if a new context is to be configured.

      Now I'm wondering whether what I'm trying to do is really that exotic or maybe if problems of this class are supposed to be tackled in a completely different way.

      Thanks in advance
      Joerg Henne

        • 1. Re: Using multiple persistence units/EntityManagerFactories
          epbernard

          you can use the Hibernate EntityManager specific programmatic API

          new Ejb3Configuration();
          ...

          • 2. Re: Using multiple persistence units/EntityManagerFactories
            hennejg

            I had a good look at Ejb3Configuration (and its source code) prior to posting the question, but came to the conclusion that I could not use it in conjunction with the container's services to scan the jars for entity beans to deploy, parse the annotations etc. However, I can see that using addAnnotatedClass(...) explicitely may do the trick.

            Thanks for your help!

            Joerg Henne

            • 3. Re: Using multiple persistence units/EntityManagerFactories
              bill.burke

              deploy your entities as you would but assign them a JNDI name(see docs). In your EJB interceptor pick the appropriate JNDI name of the EntityManager you want to access. Either inject the ENtityManager into the field of your bean using a custom annotation, or stuff the chosen entity manager into a java.lang.ThreadLocal. This way you still get the automatic session management that the EJB/Java Persistence integration gives you. Following me?

              • 4. Re: Using multiple persistence units/EntityManagerFactories
                abl

                Hi Joerg,

                do you had any success with this?

                We have exactly the same requirement: many identical dbs/datasources (ASP like application) accessed from 1 jboss instance. we also have to create PersistenceUnits on the fly at runtime.

                I also tried with EJBConfiguration.createEntityManagerFactory( name, map). Connecting to db and table creation works, but I cannot use the returned em (throws "no transaction" exception). em is not bound in JNDI.

                Thanks for feedback!
                Andy

                • 5. Re: Using multiple persistence units/EntityManagerFactories
                  qualitha

                   

                  "bill.burke@jboss.com" wrote:
                  deploy your entities as you would but assign them a JNDI name(see docs). In your EJB interceptor pick the appropriate JNDI name of the EntityManager you want to access. Either inject the ENtityManager into the field of your bean using a custom annotation, or stuff the chosen entity manager into a java.lang.ThreadLocal. This way you still get the automatic session management that the EJB/Java Persistence integration gives you. Following me?


                  Bill, can you give a little more detailed explanation on how to do this or tell me a place where to find a good documentation?

                  • 6. Re: Using multiple persistence units/EntityManagerFactories
                    hennejg

                    Sorry for the late reply.

                    So far we worked around (or rather ignored for the time being) a part of the problem, but solved the other one.

                    The part we ignored so far is the programmatic configuration of EntityManagers. I understand that it is possible to achieve this by using the APIs Bill mentioned. For now we just stuck with the persistence.xml-based configuration of the EntityManager. We will, however, implement a solution for this part of the problem by implementing a custom EntityManager deployment/configuration solution. I'll happily share the solution once we've got it.

                    The second part of the problem was solved by adding an (EJB3-) interceptor which injects an EntityManager based on the realm mentioned in my initial posting. The EntityManagers per realm to be injected, however, still have to be configured via persistence.xml - our solution implements just a dynamic choice of the persistence context to be used on a call-by-call basis until the first part of the problem is solved.
                    To implement the dynamic EntityManager injection, a Bean has to be annotated with an interceptor. The interceptor scans the class for a field annotated with a second annotation which denotes the EntityManager field to be injected. Based on the caller principal the interceptor first identifies the realm, fetches the appropriate EntityManager from JNDI and finally injects the field.

                    Please let me know if there is interest in the details of this solution, I can post some details here or on the Wiki.

                    • 7. Re: Using multiple persistence units/EntityManagerFactories
                      abl

                      Thanks for your reply.

                      We use a similar solution for now: deploying a fix number of PUs through persistence.xml and selecting the correct one at runtime through (Jboss specific) JNDI lookup:
                      jndi.lookup( "java:/EntityManagers/" + currentClient);

                      (an Interceptor would have been be a solution too, but we have some kind of context object including user, current locale, PU ... in every call anyway).

                      If you will find a solution for the programmatic configuration of em, it would be great you could share the details here! Thanks in advance!

                      • 8. Re: Using multiple persistence units/EntityManagerFactories
                        javalover75

                        Joerg - I would be interested in more details about how you solved the problem.

                        • 9. Re: Using multiple persistence units/EntityManagerFactories
                          hennejg

                          Ok, here we go...

                          Step 1: use a RealmIdentifierInterceptor like this (I've removed a few quirks which were specific to our solution):


                          /**
                          * This incerceptor intercepts calls to SessionBeans which have been annotated
                          * appropriately. It determines the principal issuing the request and identifies
                          * the target realm. It then injects the EntityManager for the chosen realm.
                          *
                          * @author Jörg Henne
                          */
                          public class RealmIdentifierInterceptor {
                          private static final Logger logger = Logger
                          .getLogger(RealmIdentifierInterceptor.class);

                          private static final String EM_JNDI_PREFIX = "java:/EntityManagers/";

                          /**
                          * A cache of injectors, indexed by class.
                          */
                          private Map<Class, Field> fieldCache = new HashMap<Class, Field>();

                          @Resource
                          javax.ejb.SessionContext sctx;

                          @AroundInvoke
                          public Object identifyRealm(InvocationContext ctx) throws Exception {
                          Field field = getFieldForInjection(ctx.getBean().getClass());
                          if (null != field) {
                          Principal callerPrincipal = sctx.getCallerPrincipal();
                          if (null == callerPrincipal)
                          throw new EJBAccessException(
                          "No caller principal to detect the realm name from");

                          String realmName = getRealmName(callerPrincipal.getName());

                          try {
                          logger.debug("Injecting EM for realm " + realmName + " during call to "
                          + ctx.getBean());

                          String emName = EM_JNDI_PREFIX + realmName;
                          EntityManager em = (EntityManager) new InitialContext().lookup(emName);

                          field.set(ctx.getBean(), em);
                          } catch (NamingException e) {
                          throw new EJBAccessException("Realm " + realmName + " not found");
                          }
                          }
                          return ctx.proceed();
                          }

                          /**
                          * Extract Realm name from Windows-style realm\\username pattern.
                          *
                          * @param principalName
                          *
                          * @return
                          */
                          private String getRealmName(String principalName) {
                          int idx = principalName.indexOf('\\');
                          if (idx <= 0)
                          throw new EJBAccessException("Can't parse the principal name "
                          + principalName);

                          return principalName.substring(0,idx);
                          }

                          /**
                          * Get the injector for the given class. Returns null, if the
                          * class doesn't declare a field annotated with
                          *
                          * @RealmBasedPersistenceContext.
                          *
                          * @param clazz
                          * @return
                          */
                          private Field getFieldForInjection(Class<? extends Object> clazz) {
                          // a null injector is possible and idicates a class which declared
                          // this interceptor, but doesn't declare a field annotated with
                          // @RealmBasedPersistenceContext.
                          if (fieldCache.containsKey(clazz))
                          return fieldCache.get(clazz);

                          Field[] fields = clazz.getDeclaredFields();
                          for (int i = 0; i < fields.length; i++) {
                          Field field = fields[ i ];
                          RealmBasedPersistenceContext annotation = field .getAnnotation(RealmBasedPersistenceContext.class);

                          if (null != annotation) {
                          logger.info("Found field for EntityManager injection: " + field
                          + " on " + field.getDeclaringClass());

                          field.setAccessible(true);
                          fieldCache.put(clazz, field);
                          return field;
                          }
                          }

                          logger.warn("Class " + clazz
                          + " is annotated with @RealmIdentifierInterceptor bit doesn't "
                          + "declare a field annotated with @RealmBasedPersistenceContext");

                          // nothing found - put null map entry to indicate invalid class
                          fieldCache.put(clazz, null);
                          return null;
                          }
                          }


                          Step 2: Define the following annotation interface:


                          import java.lang.annotation.ElementType;
                          import java.lang.annotation.Retention;
                          import java.lang.annotation.RetentionPolicy;
                          import java.lang.annotation.Target;

                          @Target({ElementType.METHOD, ElementType.FIELD})
                          @Retention(RetentionPolicy.RUNTIME)
                          public @interface RealmBasedPersistenceContext {
                          }


                          Step 3:


                          Annotate your session beans with @Interceptors({RealmIdentifierInterceptor.class})
                          and your EntityManager field with @RealmBasedPersistenceContext


                          A simple bean would thus look like this:


                          @Stateless
                          @Interceptors({RealmIdentifierInterceptor.class})
                          public class YadaBean implements Yada {

                          private static final Logger logger = Logger
                          .getLogger(AccountAdminDAOBean.class);

                          @RealmBasedPersistenceContext
                          private EntityManager em;

                          ...
                          }


                          Step 4: Implement a custom login module which is able to deal with principal names using the pattern realmName/userName.

                          Our solution is rather complicated in this area, insofar as we subclassed a specialized Principal from SimplePrincipal which parses the compound realmName/userName into its separate parts. The customized LoginModule converts an incoming SimplePrincipal into the specialized one, but clients are preferred to supply the specialized one directly, if possible.

                          The problem with this solution is that that the JBoss login mechanism with ClientLoginModule and the server-side handling silently dump the specialized Principal instance and replace it with a simple one again. Therefore it is easier to just stick with SimplePrincipals and deal with the compound principal name in your LoginModule.

                          Step 5: Implement an EJB which lists the configured realms from JNDI like this:


                          @Stateless
                          public class RealmListServiceBean implements RealmListService {
                          /*
                          * (non-Javadoc)
                          *
                          * @see de.hess.zas.business.RealmListService#listRealmNames()
                          */
                          public List listRealmNames() throws NamingException {
                          NamingEnumeration name = new InitialContext()
                          .list("java:/EntityManagers");
                          List results = new ArrayList();
                          while (name.hasMoreElements()) {
                          results.add(name.next().getName());
                          }

                          return results;
                          }
                          }


                          In order to make this work, you have to grant access to this bean to unauthenticated users, obviously. There are several ways to do this:

                          Deploy the bean outside the security domain
                          Except the bean from security
                          Allow logon using a special guest user (that's our solution)



                          I hope, this explanation was helpful.

                          Joerg Henne

                          • 10. Re: Using multiple persistence units/EntityManagerFactories
                            javalover75

                            It was very helpful - thank you for taking the time to share.