The JNDI ENC




The JNDI ENC

The ENC has been around in the EJB specification since the early 1.0 days. It began as a local JNDI namespace that was specific to an EJB container. Developers could define aliases to resources, EJBs, and environment entries in the JNDI ENC through EJB XML deployment descriptors. These aliases could then be looked up directly in JNDI within business logic. In EJB 3.0, this mechanism was enhanced so that JNDI ENC references could be injected directly into the fields of a bean class. Annotations are the primary mechanism for doing this, but XML deployment descriptor support is available for those who wish to use that abstraction.

What Can Be Registered in the JNDI ENC?

Many different items can be bound to the ENC: references to any EJB interface, a JMS queue or topic destination, JMS connection factories, data sources, any JCA resource, and even primitive values. Java EE services such as javax.transaction.UserTransaction, javax.ejb.TimerService, and org.omg.CORBA.ORB are also available in the ENC.

How Is the JNDI ENC Populated?

The ENC's JNDI namespace is populated in two separate ways: via XML or via annotations. Any reference that you declare in XML to a service or resource automatically populates the JNDI ENC with the reference's name. Any environment annotation that you use in your bean class also causes the ENC to be populated. Once an item is bound to the JNDI ENC of the EJB container, it can be referenced by a JNDI lookup.

XML Population

To illustrate how XML population works, let's define a reference to the stateless session bean we wrote in Chapter 11. Here we define a local interface reference to the ProcessPayment EJB for the TravelAgent EJB:

<ejb-jar>
   <enterprise-beans>
      <session>
         <ejb-name>TravelAgentBean</ejb-name>
         <ejb-local-ref>
            <ejb-ref-name>ejb/ProcessPayment 
</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <local>com.titan.processpayment.ProcessPaymentLocal</local>
            <ejb-link>ProcessPaymentBean</ejb-link>
         </ejb-local-ref>
   </enterprise-beans>
</ejb-jar>

The <ejb-local-ref> element tells the EJB container that the travelAgentBean wants a reference to the ProcessPayment EJB. A reference to this bean is registered in the travelAgentBean's JNDI ENC under the name ejb/ProcessPayment. This is defined by the <ejb-ref-name> element. Other referenceable things, like resources and JMS destinations, have similar XML elements such as <ejb-local-ref> to specify how and where the reference will be bound into their JNDI ENCs. Each service type in Java EE has its own reference syntax. We'll see examples of all of them in this chapter.

Annotation Population

Each referenceable type also has a corresponding annotation that can be used as an alternative to XML. If you specify these annotations on the bean class, they will cause the JNDI ENC to be populated with the information defined in the annotation:

import javax.annotation.EJB;

@Stateful
@EJB(name="ejb/ProcessPayment", 
     beanInterface=ProcessPaymentLocal.class,
     beanName="ProcessPaymentBean") 

public class TravelAgentBean implements TravelAgentRemote {
...
}

In this example, we are registering a reference to the ProcessPayment EJB under the ejb/ProcessPayment name. Business logic running inside the travelAgentBean is able to do JNDI lookups to find this reference. Each environment annotation, such as @javax.annotation.EJB , has a name( ) attribute that specifies the JNDI ENC name to which you want the service reference to be bound. The second half of this chapter describes all the details of each of these different environment annotations.

How Are Things Referenced from the ENC?

Anything registered in the JNDI ENC can be looked up by name under the java:comp/env context. The comp part of this name corresponds to component. The JNDI name resolves to a different context depending on where you invoke the lookup. For example, if you invoke jndi.lookup("java:comp/env") within the travelAgentBean, you will get that EJB container's ENC. If you do the same within ProcessPaymentBean, you will get a different ENC registry specific to that bean. The application server knows what ENC is active when you perform the lookup.

@Stateful
@EJB(name="ejb/ProcessPayment", 
  beanInterface=ProcessPaymentLocal.class,
     beanName="ProcessPaymentBean")
public class TravelAgentBean implements TravelAgentRemote {

   public TicketDO bookPassage(CreditCardDO card, double amount) {
      ProcessPaymentLocal payment = null;
      try {
         javax.naming.InitialContext ctx = new InitialContext( );
         payment = (ProcessPaymentLocal)
                 ctx.lookup("java:comp/env/ejb/ProcessPayment");
 

      } catch (javax.naming.NamingException ne) {
         throw new EJBException(ne);
      }
      payment.process(card, customer, amount);
...
}

In this example, the bookPassage( ) method from our TravelAgent EJB needs a reference to the ProcessPayment EJB so that it can bill the customer for the reservation. A reference to the ProcessPayment EJB was created in the TravelAgent's ENC by annotating the bean class with the @EJB annotation. The preceding code does a JNDI lookup to find this reference. While the ProcessPayment.process( ) method is invoked, the java:comp/env/ejb/ProcessPayment reference is no longer available because the ProcessPayment's ENC is active instead of the TravelAgent's ENC.

Using EJBContext

In Chapters 11 and 12, we talked a little bit about the javax.ejb.SessionContext and javax.ejb.MessageDrivenContext interfaces. Both extend the javax.ejb.EJBContext and they can be used to look up ENC entries. The EJBContext interface has a convenience ENC lookup method. This method is a bit simpler than a direct JNDI lookup because it does not throw a checked exception and it takes a relative name into the ENC instead of the full java:comp/env string we saw before. SessionContext or MessageDrivenContext can be injected into your session or message-driven beans by using the @javax.annotation.Resource annotation.

@Stateful
@EJB(name="ejb/ProcessPayment", 
  beanInterface=ProcessPaymentLocal.class,
     beanName="ProcessPaymentBean")
public class TravelAgentBean implements TravelAgentRemote {
   
@Resource private javax.ejb.SessionContext ejbContext;

   public TicketDO bookPassage(CreditCardDO card, double amount) {
   ProcessPaymentLocal payment = (ProcessPaymentLocal)
           ejbContext.lookup("ejb/ProcessPayment");
      payment.process(card, customer, amount);
...
}

This example uses the EJBContext.lookup( ) method to look up the travelAgentBean's reference to ProcessPayment. This context object is injected into the ejbContext field using the @Resource annotation. You do not append the java:comp/env string to the name when performing the lookup but instead use the relative name defined in the annotation or XML reference.

Annotation injection

Instead of an ENC lookup, the ProcessPayment EJB reference can be injected directly into a member variable of the TravelAgent EJB. This injection can be done through environment annotations or an XML deployment descriptor fragment:

@Stateful
public class TravelAgentBean implements TravelAgentRemote {
   @EJB private ProcessPaymentLocal payment;

...
}

By using the @javax.ejb.EJB annotation on the payment field of the travelAgentBean class, the EJB container will automatically inject a reference to the ProcessPayment EJB directly into the payment field when the travelAgent bean instance is created. Alternatively, if you do not like this form of injection, the specification also supports injecting via a bean setter method:

@Stateful
public class TravelAgentBean implements TravelAgentRemote {
   private ProcessPaymentLocal payment;

   @EJB public void setProcessPayment(ProcessPaymentLocal payment) {
   this.payment = payment;
}

Unlike the previous example, when the travelAgentBean instance is allocated, the EJB container will instead invoke the setProcessPayment( ) method, passing in the EJB reference as a parameter. This pattern works for all other injection annotations discussed in this chapter. Setter method injection is more verbose than direct field injection, but its advantage is that it can be mocked more easily in unit tests.

A number of different environment annotations such as @EJB are described in detail in the second half of this chapter. All of them function similarly and follow the same usage patterns as@EJB.

Default ENC name

Annotating a field or a setter method of a bean class also creates an entry in the JNDI ENC for the injected element. This is true for all environment annotations, not just @EJB. If the name( ) attribute of the injection annotation is specified, then the reference is stored in the ENC under that name. If no name is specified, then the ENC name is extracted from the name of the annotated field or setter method. In this case, a default ENC name is derived from the fully qualified class name of the field or method, as well as the base name of the field or method. So, for the payment field of the previous example, the ENC name would be com.titan.travelagent.TravelAgentBean/payment . For the setProcessPayment( ) method, the default ENC name would be com.titan.travelagent.TravelAgentBean/processPayment . These injected EJB references can then be looked up in JNDI under java:comp/env/com.titan.travelagent.TravelAgentBean/payment and java:comp/env/com.titan.travelagent.TravelAgentBean/processPayment , respectively. The ENC name becomes very important when you want to override an injection annotation within XML.

XML injection

If you prefer not to use annotations to initialize the fields of your bean class, then the <injection-target> element is available to you in your ejb-jar.xml deployment descriptor:

<ejb-jar>
   <enterprise-beans>
      <session>
         <ejb-name>TravelAgentBean</ejb-name>
         <ejb-local-ref>
            <ejb-ref-name>ProcessPayment</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <local>com.titan.processpayment.ProcessPaymentLocal</local>
            <ejb-link>ProcessPaymentBean</ejb-link>
         <injection-target>
               <injection-target-class>
                   com.titan.travelagent.TravelAgentBean
               </injection-target-class>
               <injection-target-name>payment</injection-target-name>
            </injection-target>
         </ejb-ref>
   </enterprise-beans>
</ejb-jar>

Each XML environment element such as <ejb-local-ref> can use <injection-target> to populate a field or call a setter method with the referenced item. The <injection-target-class> element is the class where your field or method is declared. This may seem unnecessarily verbose, but this becomes important when there are inheritance hierarchies. The <injection-target-name> specifies the target field or method name into which you want the reference injected. In this case, we're injecting into the payment field. If you wanted to inject into a setter method instead, it would be processPayment .

You cannot inject into a field and method with the same base name. For instance, if you had a setProcessPayment( ) method as well as a processPayment field, you could not define injections into both of those items, as they will represent the same ENC name and will not be distinguishable by the EJB container.


XML overrides

Using injection annotations is sometimes considered hardcoding configuration into the code of your bean class. Although there is an element of hardcoding here, the EJB specification allows you to override injection annotations via the XML deployment descriptor. Let's re-examine our use of the @EJB annotation:

@Stateful
public class TravelAgentBean implements TravelAgentRemote {
   @EJB private ProcessPaymentLocal 
 payment;

...
}

In the original deployment of the TravelAgent EJB, the EJB container could figure out what EJB reference to inject based on the type of the annotated payment field. ProcessPaymentLocal was unique across the application. What if in a new deployment, multiple process payment engines were deployed into the same application? You might want to configure, per deployment of your application, which process payment engine the travelAgentBean used. You can override this annotation within XML:

<ejb-jar>
   <enterprise-beans>
      <session>
         <ejb-name>TravelAgentBean</ejb-name>
         <ejb-local-ref>
         <ejb-ref-name>
               come.titan.travelagent.TravelAgentBean/payment
            </ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <local>com.titan.processpayment.ProcessPaymentLocal
 
</local>
         <ejb-link>MasterCardProcessPaymentBean</ejb-link>
         </ejb-local-ref>
   </enterprise-beans>
</ejb-jar>

In this example, we are providing a more exact mapping for the @EJB annotation within XML. The <ejb-ref-name> must match the default ENC name of the injected field; this is how the EJB container knows you are overriding an annotated field. The <ejb-link> element provides a more specific reference to the process payment engine that the TravelAgent EJB will use. If the name( ) attribute was used with the @EJB annotation, then <ejb-ref-name> would have to match that value:

@Stateful
public class TravelAgentBean implements TravelAgentRemote {
   @EJB(name="ejb/ProcessPayment") 

private ProcessPaymentLocal payment;

...
}

The @EJB annotation tells the EJB container that it wants to inject an EJB with the ProcessPaymentLocal interface into the payment field. This EJB reference is registered in the ENC under the ejb/ProcessPayment entry. The XML must use this ENC name to override what is injected:

<ejb-jar>
   <enterprise-beans>
      <session>
         <ejb-name>TravelAgentBean</ejb-name>
         <ejb-local-ref>
         <ejb-ref-name>ejb/ProcessPayment</ejb-ref-name>
            <ejb-ref-type>Session</ejb-ref-type>
            <local>com.titan.processpayment.ProcessPaymentLocal</local>
         <ejb-link>MasterCardProcessPaymentBean</ejb-link>
         </ejb-local-ref>
   </enterprise-beans>
</ejb-jar>

The same ejb/ProcessPayment name is referenced using the <ejb-ref-name> element.

XML always takes precedence over annotation metadata. XML provides the means to reconfigure hardcoded annotation configuration.


Injection and inheritance

It is possible for a bean class to be part of a class hierarchy. If any fields or methods have injection annotations on them, they will still be populated, but certain injection rules are followed:

public class BaseClass 
 {

   @Resource DataSource data;

   @EJB(beanName="ProcessPaymentBean 
 ")
   public void setProcessPayment(ProcessPaymentLocal pp) {
   ...
   }
}

@Stateless
public class MySessionBean 

extends BaseClass implements MySessionRemote {
...
}

In this example, we have a stateless session bean class that inherits from a base class. All instances of MySessionBean would have the appropriate resource injected into the base class's data field as well as the setProcessPayment( ) method. It is possible to change what is injected into the setProcessPayment( ) method by reimplementing and overriding it in the subclass:

@Stateless
public class MySessionBean extends BaseClass implements MySessionRemote {
   @EJB(beanName="AcmeProcessPayment") 

   public void setProcessPayment(ProcessPaymentLocal pp) {
      ...
   }
...
}

The ProcessPaymentBean would no longer be injected into the setProcessPayment( ) method. Instead, the new overridden reference, AcmeProcessPayment, would be injected. There is one exception to this rule. If the setProcessPayment( ) method in the BaseClass was a private method rather than a protected or public method, then the base class would still be injected with the old reference:

Public class BaseClass {

   @Resource DataSource data;

   @EJB(beanName="ProcessPaymentBean")
   private void setProcessPayment(ProcessPaymentLocal pp) {
   ...
   }
}
@Stateless
public class MySessionBean extends BaseClass implements MySessionRemote {
   @EJB(beanName="AcmeProcessPayment") 

   public void setProcessPayment(ProcessPaymentLocal 
 pp) {
      ...
   }
...
}


So, in the previous example, both setProcessPayment( ) methods would be invoked and set with a different ProcessPaymentLocal reference. The BaseClass 's setProcessPayment( ) method would get a reference to the ProcessPaymentBean , and the setProcessPayment( ) method of MySessionBean would get AcmeProcessPayment.