-
1. Re: Using multiple persistence units/EntityManagerFactories
epbernard May 30, 2006 1:19 PM (in response to hennejg)you can use the Hibernate EntityManager specific programmatic API
new Ejb3Configuration();
... -
2. Re: Using multiple persistence units/EntityManagerFactories
hennejg May 31, 2006 5:55 AM (in response to 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 May 31, 2006 7:59 AM (in response to hennejg)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 Jun 9, 2006 3:27 AM (in response to hennejg)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 Jun 20, 2006 3:34 AM (in response to hennejg)"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 Jun 20, 2006 5:03 AM (in response to 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 Jun 20, 2006 6:54 AM (in response to hennejg)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 Jun 21, 2006 7:05 PM (in response to hennejg)Joerg - I would be interested in more details about how you solved the problem.
-
9. Re: Using multiple persistence units/EntityManagerFactories
hennejg Jun 23, 2006 6:13 AM (in response to 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 Jun 25, 2006 6:41 PM (in response to hennejg)It was very helpful - thank you for taking the time to share.