EJB metadata and the jndi policy decorators
jaikiran Dec 11, 2009 7:59 AMWhile integrating no-interface metadata support in AS i see that there's one more stumbling block - the jndi policy decorator(s). From what i see of those decorators, they are meant to provide (decorated) jndi names on the metadata:
* Decorate a JBossSessionBeanMetaData with the ability to resolve JNDI Names * based on a specified JNDI Binding Policy, so any getter of a JNDI * name will never return null.
The JBossSessionPolicyDecorator looks like this:
public class JBossSessionPolicyDecorator<T extends JBossSessionBeanMetaData> extends JBossServiceBeanMetaData implements ResolveableJndiNameJbossSessionBeanMetadata { ... private T delegate; private DefaultJndiBindingPolicy jndiPolicy; @SuppressWarnings("unchecked") public JBossSessionPolicyDecorator(T delegate, DefaultJndiBindingPolicy jndiPolicy) { assert delegate != null : "delegate is null"; assert jndiPolicy != null : this.getClass().getSimpleName() + " requires a specified " + DefaultJndiBindingPolicy.class.getSimpleName(); // Disallow double-wrapping if(delegate instanceof JBossSessionPolicyDecorator) { JBossSessionPolicyDecorator<T> d =(JBossSessionPolicyDecorator<T>)delegate; delegate = d.getDelegate(); } this.delegate = delegate; this.jndiPolicy = jndiPolicy; } ... // overrides everything from the JBossSessionBeanMetaData to pass control to the delegate // just some example overriden methods @Override public BusinessLocalsMetaData getBusinessLocals() { return delegate.getBusinessLocals(); } @Override public BusinessRemotesMetaData getBusinessRemotes() { return delegate.getBusinessRemotes(); } @Override public CacheConfigMetaData getCacheConfig() { return delegate.getCacheConfig(); }
And then when we want to decorate the merged metadata, we create a new instance of this decorator (notice that the decorator itself is a metadata instance):
// Create a Session JNDI Policy Decorated Bean JBossSessionBeanMetaData decoratedBean = new JBossSessionPolicyDecorator<JBossSessionBeanMetaData>(sessionBean, policy); // then add as attachment to deployment unit du.addAttachment(...,decoratedBean);
The real problem here is that the decorator itself extends from the metadata (i.e. it's an metadata itself) and we instantiate this decorated metadata and pass it along. Most of the methods in this decorated metadata have been overriden to pass the control to the delegate metadata from which this is constructed. However, if any new methods gets added (like we just added for the EJB3.1 support), then unless someone goes and updates this class to override such methods, this decorated metadata is going to return incorrect information. For example (pseudo code):
JBossSessionBean31MetaData original = new JBossSessionBean31MetaData(); original.setNoInterfaceBean(true); assertTrue(original.isNoInterfaceBean()); // succeeds // now decorate (remember pseudo code, but you get the idea) JBossSessionBean31MetaData decorated = new JBossSessionPolicyDecorator(original,jndiPolicy); // now check whether the no-interface information was kept intact assertTrue(decorated.isNoInterfaceBean()); // FAILS
So unless i go and update the decorator to override this method, things are not going to work:
public class JBossSession31PolicyDecorator<T extends JBossSession31BeanMetaData> extends JBossSessionPolicyDecorator { @Overriden public boolean isNoInterfaceBean() { delegate.isNoInterfaceBean; } ...
This is going to be an issue with the introduction of more and more 3.1 metadata support (for ex: async-methods).
So here's what i was thinking:
The decorator really just needs to update/decorate the metadata with appropriate JNDI names. It shouldn't really mess with anything else. So why not have something like:
public interface EnterpriseBeanJNDINameDecorator<T extends JBossEnterpriseBeanMetaData> { /** * Updates the <code>metaData</code> with the ability to resolve JNDI names. * It's upto the implementations of this interface to either: * <ul> * <li>Create a new instance of metadata and add the ability to resolve JNDI names and return it * </li> * <li>Or update the passed <code>metaData</code> with the ability to resolve JNDI names * and return it back</li> * </ul> * @param metaData The metaData which will be decorated with JNDI names * @return Returns the updated metadata */ T decorate(T metaData); }
Then an implementation of the decorator for the session beans would look like:
/** * JBossSessionBeanMetaDataJNDINameDecorator * * Decorates a JBossSessionBeanMetaData with the ability to resolve JNDI Names * based on a specified JNDI Binding Policy. * * @author Jaikiran Pai * @version $Revision: $ */ public class JBossSessionBeanMetaDataJNDINameDecorator<T extends JBossSessionBeanMetaData> implements EnterpriseBeanJNDINameDecorator<T> { private DefaultJndiBindingPolicy jndiPolicy; public JBossSessionBeanMetaDataJNDINameDecorator(DefaultJndiBindingPolicy jndiPolicy) { this.jndiPolicy = jndiPolicy; } /** * @see org.jboss.metadata.ejb.jboss.jndipolicy.plugins.EnterpriseBeanJNDINameDecorator#decorate(org.jboss.metadata.ejb.jboss.JBossEnterpriseBeanMetaData) */ @Override public T decorate(T sessionBeanMetaData) { // create a resolveable metadata ResolveableJndiNameJbossSessionBeanMetadata resolveableJndiNameMetaData = new JNDIPolicyBasedResolveableJndiNameJBossSessionBeanMetaDataImpl<T>( sessionBeanMetaData, this.jndiPolicy); // Now decorate the metadata (i.e. Update all the relevant JNDI names in the metadata) // local jndi name String localJndiName = resolveableJndiNameMetaData.determineResolvedLocalBusinessDefaultJndiName(); if (localJndiName != null) { sessionBeanMetaData.setLocalJndiName(localJndiName); } // remote jndi name String jndiName = resolveableJndiNameMetaData.determineResolvedRemoteBusinessDefaultJndiName(); if (jndiName != null) { sessionBeanMetaData.setJndiName(jndiName); } // home jndi name String homeJndiName = resolveableJndiNameMetaData.determineResolvedRemoteHomeJndiName(); if (homeJndiName != null) { sessionBeanMetaData.setHomeJndiName(homeJndiName); } // local home jndi name String localHomeJndiName = resolveableJndiNameMetaData.determineResolvedLocalHomeJndiName(); if (localHomeJndiName != null) { sessionBeanMetaData.setLocalHomeJndiName(localHomeJndiName); } // mapped name String mappedName = this.getMappedName(sessionBeanMetaData, resolveableJndiNameMetaData); if (mappedName != null) { sessionBeanMetaData.setMappedName(mappedName); } // return the decorated metadata return sessionBeanMetaData; }
This decorator uses the jndipolicy and an implementation of ResolveableJndiNameJbossSessionBeanMetadata to decorate the bean with the JNDI names. The implementation of ResolveableJndiNameJbossSessionBeanMetadata looks like this (i just moved it out of the existing JBossSessionPolicyDecorator):
public class JNDIPolicyBasedResolveableJndiNameJBossSessionBeanMetaDataImpl<T extends JBossSessionBeanMetaData> implements ResolveableJndiNameJbossSessionBeanMetadata { ... /** * Constructor * * @param bean The enterprise bean metadata whose jndi name has to be transiently resolvable * @param jndiPolicy The JNDI policy which will be used to resolve the jndi name(s) of the <code>bean</code> */ public JNDIPolicyBasedResolveableJndiNameJBossSessionBeanMetaDataImpl(T bean, DefaultJndiBindingPolicy jndiPolicy) { this.sessionBean = bean; this.jndiPolicy = jndiPolicy; } // ... THIS implementation has just been moved out of the current JBossSessionPolicyDecorator, so // there's nothing new here. No point posting all the code in the forum, so just one method has been // posted as an example /** * Returns the resolved JNDI target to which the * default EJB3.x Local Business interfaces are to be bound * * @return */ @Override public String determineResolvedLocalBusinessDefaultJndiName() { String s = this.sessionBean.getLocalJndiName(); if (s != null && s.length() > 0) return s; return getJNDIPolicy().getJndiName(getEjbDeploymentSummary(), KnownInterfaces.LOCAL, KnownInterfaceType.BUSINESS_LOCAL); }
So now that we have a decorator (which is *not* an metadata in itself), we can use it to decorate the existing/original metadata as follows:
JBossSessionBean31MetaData original = new JBossSessionBean31MetaData(); original.setNoInterfaceBean(true); assertTrue(original.isNoInterfaceBean()); // succeeds // now decorate // 1. create a decorator EnterpriseBeanJNDINameDecorator<JBossSessionBean31MetaData> decorator = new JBossSessionBeanMetaDataJNDINameDecorator<JBossSessionBean31MetaData>(new BasicJndiBindingPolicy()); // decorate with this decorator JBossSessionBean31MetaData decorated = decorator.decorate(original); // now check whether the no-interface information was kept intact assertTrue(decorated.isNoInterfaceBean()); // Now passes
Overall, the difference here is that we have now moved away from the decorator being a metadata in itself.
My local testcases (around no-interface) are now passing with these changes. There are some more things that i'll have to work on (for example: a decorator for service beans etc...), but before i do anything around this, i wanted to have other's opinion on this change. Any thoughts on how we should proceed? Does this look OK?