4 Replies Latest reply on Dec 29, 2009 2:15 AM by jaikiran

    EJB metadata and the jndi policy decorators

    jaikiran

      While 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?



        • 1. Re: EJB metadata and the jndi policy decorators
          wolfc

          First there is no no-interface bean. Any EJB can have a no-interface view.

          The actual decoration of bean metadata with JNDI resolving logic doesn't really work out as we would like it to be. We may have to switch to another construct.

          • 2. Re: EJB metadata and the jndi policy decorators
            jaikiran

             

            "wolfc" wrote:
            First there is no no-interface bean. Any EJB can have a no-interface view.

            Yeah, a wrong term :)



            • 3. Re: EJB metadata and the jndi policy decorators
              jaikiran

              As discussed over IRC, we will be moving away from the decorated metadata. As a first step, JNDI name resolvers have been introduced in jboss-metadata-ejb:

               

              http://anonsvn.jboss.org/repos/jbossas/projects/metadata/ejb/trunk/src/main/java/org/jboss/metadata/ejb/jboss/jndi/resolver/spi/

               

              The default implementations use a JNDI binding policy to resolve the jndi names:

               

              http://anonsvn.jboss.org/repos/jbossas/projects/metadata/ejb/trunk/src/main/java/org/jboss/metadata/ejb/jboss/jndi/resolver/impl/

               

              The test cases for these are here http://anonsvn.jboss.org/repos/jbossas/projects/metadata/ejb/trunk/src/test/java/org/jboss/metadata/ejb/test/jndiresolver/unit/SessionBeanJNDINameResolverTestCase.java

               

              So going forward, any component which requires the JNDI names for different views (like remote home, local home, default business remote etc...) of a EJB, should use these resolvers.

               

              As decided we will have to stop decorating the metadata (through the JNDI policy decorator). This is simple, just remove the EjbMetadataJndiPolicyDecoratorDeployer deployer from the configs. But the challenge is, as Carlo pointed out, there's lots of code (even outside EJB3 project) which relies on the metadata to be decorated. The existing code relies on APIs like metadata.getJNDIName to get hold of the jndi name of the bean. This will no longer work once we stop decorating the metadata. Any thoughts on how we should move ahead on this?

              • 4. Re: EJB metadata and the jndi policy decorators
                jaikiran

                jaikiran wrote:

                 

                As discussed over IRC, we will be moving away from the decorated metadata.

                ...

                 

                As decided we will have to stop decorating the metadata (through the JNDI policy decorator). This is simple, just remove the EjbMetadataJndiPolicyDecoratorDeployer deployer from the configs. But the challenge is, as Carlo pointed out, there's lots of code (even outside EJB3 project) which relies on the metadata to be decorated. The existing code relies on APIs like metadata.getJNDIName to get hold of the jndi name of the bean. This will no longer work once we stop decorating the metadata. Any thoughts on how we should move ahead on this?

                 

                I analyzed the EJB3 code and ran some tests to see what areas are impacted within the EJB3 project, if we get rid of the EjbMetadataJndiPolicyDecoratorDeployer which creates a decorated metadata. Here's what i found:

                 

                1) Within EJB3 code (like proxy-impl) we expect to get the (resolved) jndi names through the decorated metadata. So we have calls like metadata.getJNDIName which we expect to return the correct resolved default business interface jndi name.

                 

                2) We have the MappedReferenceMetaDataResolverDeployer which uses the deprecated metadata.determine*JNDIName APIs  to get hold of the resolved JNDI names. If we stop decorating the metadata, these calls will no longer return the correct results.

                 

                Here's how i plan to tackle these 2 areas:

                 

                For #1, from what i see in the code, we will have to continue to support the metadata.getJNDIName type of calls to return the *resolved* JNDI names. This is because there are numerous places where such calls exist. Ideally, we could have just gone ahead and change such code to use the new resolvers, but i don't think that can be done easily. So i plan to introduce a variant of the EjbMetadataJndiPolicyDecoratorDeployer which sets the appropriate jndi names using the new resolvers as follows:

                 

                /**
                 * JBossMetaDataJNDINameDecorator
                 * 
                 * Responsible for setting the resolved JNDI names on the metadata, so 
                 * that the code relying on calls like metadata.getJNDIName will 
                 * get the resolved jndi name.
                 * 
                 * Note that this deployer is here to provide backward compatibility
                 * and any new code which expects resolved JNDI names, is expected
                 * to use the JNDI name resolvers (ex: an implementation of {@link SessionBeanJNDINameResolver}) 
                 * @author Jaikiran Pai
                 * @version $Revision: $
                 */
                public class JBossMetaDataJNDINameDecoratorDeployer extends AbstractDeployer
                {
                
                   
                   /**
                    * Sets the resolved JNDI names in the metadata
                    *
                    * @see org.jboss.deployers.spi.deployer.Deployer#deploy(org.jboss.deployers.structure.spi.DeploymentUnit)
                    */
                   public void deploy(DeploymentUnit du) throws DeploymentException
                   {
                      // Obtain the Merged Metadata
                      JBossMetaData md = du.getAttachment(INPUT, JBossMetaData.class);
                
                      // If metadata's not present as an attachment, return
                      if (md == null)
                      {
                         return;
                      }
                
                      // If this is not an EJB3 Deployment, return
                      if (!md.isEJB3x())
                      {
                         return;
                      }
                
                      JBossEnterpriseBeansMetaData beans = md.getEnterpriseBeans();
                      if (beans == null)
                      {
                         return;
                      }
                      DefaultJndiBindingPolicy jndiBindingPolicy = DefaultJNDIBindingPolicyFactory.getDefaultJNDIBindingPolicy();
                      for (JBossEnterpriseBeanMetaData bean : beans)
                      {
                         // let's only do this for SLSB, SFSB and @Service
                         // Ignore Entity beans or MDB
                         if (bean.isSession() || bean.isService())
                         {
                            JBossSessionBeanMetaData sessionBean = (JBossSessionBeanMetaData) bean;
                            SessionBeanJNDINameResolver<JBossSessionBeanMetaData> sessionBeanJNDINameResolver = new JNDIPolicyBasedSessionBeanJNDINameResolver<JBossSessionBeanMetaData>(
                                  jndiBindingPolicy);
                            // set resolved jndi-name (default remote business jndi-name)
                            String defaultBusinessRemoteJNDIName = sessionBeanJNDINameResolver.resolveRemoteBusinessDefaultJNDIName(sessionBean);
                            if (defaultBusinessRemoteJNDIName != null)
                            {
                               sessionBean.setJndiName(defaultBusinessRemoteJNDIName);
                            }
                            // set resolved local-jndi-name
                            String defaultBusinessLocalJNDIName = sessionBeanJNDINameResolver.resolveLocalBusinessDefaultJNDIName(sessionBean);
                            if (defaultBusinessLocalJNDIName != null)
                            {
                               sessionBean.setLocalJndiName(defaultBusinessLocalJNDIName);
                            }
                            // set resolved local-home-jndi-name
                            String localHomeJNDIName = sessionBeanJNDINameResolver.resolveLocalHomeJNDIName(sessionBean);
                            if (localHomeJNDIName != null)
                            {
                               sessionBean.setLocalHomeJndiName(localHomeJNDIName);
                            }
                            // set resolved remote-home-jndi-name
                            String remoteHomeJNDIName = sessionBeanJNDINameResolver.resolveRemoteHomeJNDIName(sessionBean);
                            if (remoteHomeJNDIName != null)
                            {
                               sessionBean.setHomeJndiName(remoteHomeJNDIName);
                            }
                         }
                      }
                      
                   }
                }
                
                

                 

                So with this deployer in place, the calls like metadata.getJNDIName which expect resolved jndi names, can continue to exist. And it's recommended that any new code will now use the new resolvers.

                 

                For #2, i am planning to change the MappedReferenceMetaDataResolverDeployer to use the new resolvers, since it is after all using the deprecated metadata.determine*JNDIName APIs.

                 

                With these changes, i expect the testsuite to pass. Any thoughts on these proposed changes?