10.5. Describing BeansThe beans contained in a JAR file are described within the deployment descriptor's enterprise-beans element. So far, we've only talked about deployment descriptors for a single bean, but there's no reason that you can't package several beans in a JAR file and describe them all within a single deployment descriptor. We could, for example, have deployed the TravelAgent, ProcessPayment, Cruise, Customer, and Reservation beans in the same JAR file. The deployment descriptor would look something like this: <?xml version="1.0"?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <description> This Deployment includes all the beans needed to make a reservation: TravelAgent, ProcessPayment, Reservation, Customer, Cruise, and Cabin. </description> <enterprise-beans> <session> <ejb-name>TravelAgentBean</ejb-name> <remote>com.titan.travelagent.TravelAgent</remote> ... </session> <entity> <ejb-name>CustomerBean</ejb-name> <remote>com.titan.customer.Customer</remote> ... </entity> <session> <ejb-name>ProcessPaymentBean</ejb-name> <remote>com.titan.processpayment.ProcessPayment</remote> ... </session> ... </enterprise-beans> <assembly-descriptor> ... </assembly-descriptor> ... </ejb-jar> In this descriptor, the enterprise-beans element contains two session elements and one entity element describing the three beans. Other elements within the entity and session elements provide detailed information about the beans; as you can see, the ejb-name element defines the bean's name. We'll discuss all of the things that can go into a bean's description later. Multiple bean deployments have the advantage that they can share assembly information, which is defined in the assembly-descriptor element that follows the enterprise-beans element. In other words, beans can share security and transactional declarations, making it simpler to deploy them consistently. For example, deployment is easier if the same logical security roles control access to all the beans, and it's easiest to guarantee that the roles are defined consistently if they are defined in one place. It's also easier to ensure that the transactional attributes are applied consistently to all beans because you can declare them all at the same time. 10.5.1. Session and Entity BeansThe session and entity elements, which are used to describe session and entity beans, usually contain many elements nested within them, but the lists of allowable subelements are similar. Therefore, we'll discuss the session and entity elements together. Like the ebj-jar element itself, a session or an entity element can optionally have description, display-name, small-icon, and large-icon elements. These are fairly self-explanatory and, in any case, mean the same as they did for the ejb-jar element. The description lets you provide a comment that describes the bean; the display-name is used by deployment tools to represent the bean; and the two icons are used to represent the bean in visual environments. The icons must point to JPEG or GIF images within the JAR file. The other elements are more interesting:
<cmp-field> <description>This is the primary key</description> <field-name>id</field-name> </cmp-field> <cmp-field> <field-name>name</field-name> </cmp-field> <cmp-field> <field-name>deckLevel</field-name> </cmp-field> <cmp-field> <field-name>ship</field-name> </cmp-field> <cmp-field> <field-name>bedCount</field-name> </cmp-field>
10.5.2. Specifying Primary KeysAn entity bean does not always have to use a custom key class as a primary key. If there's a single field in the bean that can serve naturally as a unique identifier, you can use that field as the primary key without having to create a custom key. In the Cabin bean, for example, the primary key type was the CabinPK, which mapped to the bean class field id as shown here (the CabinBean is using bean-managed persistence to better illustrate): public class CabinBean implements javax.ejb.EntityBean { public int id; public String name; public int deckLevel; public int ship; public int bedCount; public CabinPK ejbCreate(int id) { this.id = id; return new CabinPk(id); } ... } Instead of using the custom CabinPK class, we could have used the appropriate primitive wrapper, java.lang.Integer, and defined the CabinBean as: public class CabinBean implements javax.ejb.EntityBean { public int id; public String name; public int deckLevel; public int ship; public int bedCount; public Integer ejbCreate(int id){ this.id = id; return new Integer(id); } ... } This simplifies things a lot. Instead of taking the time to define a custom primary key like CabinPK, we simply use the appropriate wrapper. To do this, we need to add a primkey-field element to the Cabin bean's deployment descriptor, so that it knows which field to use as the primary key. We also need to change the prim-key-class element to state that the Integer class is being used to represent the primary key. The following code shows how the Cabin bean's deployment descriptor would need to change to use Integer as the primary key field: <entity> <description> This Cabin enterprise bean entity represents a cabin on a cruise ship. </description> <ejb-name>CabinBean</ejb-name> <home>com.titan.cabin.CabinHome</home> <remote>com.titan.cabin.Cabin</remote> <ejb-class>com.titan.cabin.CabinBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <primkey-field>id</primkey-field> <reentrant>False</reentrant> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>deckLevel</field-name></cmp-field> <cmp-field><field-name>ship</field-name></cmp-field> <cmp-field><field-name>bedCount</field-name></cmp-field> </entity> Simple primary key fields are not limited to the primitive wrapper classes (Byte, Boolean, Integer, etc.); any container-managed field can be used as a primary key as long as it's serializable. String types are probably the most common, but other types, such as java.lang.StringBuffer, java.util.Date, or even java.util.Hashtable are also valid. Custom types can also be primkey-fields providing that they are serializable. Of course, common sense should be used when choosing a primary key: because it is used as an index to the data in the database, it should be lightweight. Here's code for a bean that uses a Date as its primary key: // bean class that uses Date as a primary key public class HypotheticalBean implements javax.ejb.EntityBean { public Date creationDate; ... public Date ejbCreate() { creationDate = new Date(); return creationDate; } ... } And here's the corresponding section of the deployment descriptor: // primkey-field declaration for the Hypothetical bean ... <entity> <ejb-name>HypotheticalBean</ejb-name> ... <prim-key-class>java.util.Date</prim-key-class> <primkey-field>creationDate</primkey-field> <reentrant>False</reentrant> <cmp-field><field-name>creationDate</field-name></cmp-field> ... </entity> Throughout the book we use custom compound primary keys, like ShipPK and CabinPK, instead of using simple primary keys. This may seem strange because these custom primary keys only wrap a single field, usually an integer, which could have been represented by an Integer and used as the primkey-field. The reason we use custom primary keys is simple: encapsulation. If the primary key fields of the beans change over time, using a custom key hides the changes from client applications that use the key. If, for example, the CabinBean changed to use both a String and a long primitive as the primary key fields instead of a single integer field (id), the Cabin bean's custom primary key class (CabinPK) would hide this change from the client application. If, however, we had used a primkey-field of java.lang.Integer, any client applications that use the findByPrimaryKey() method (and other similar operations involving the key) would have to be modified. 10.5.2.1. Deferring primary key definitionWith container-managed persistence, it's also possible for the bean developer to defer defining the primary key, leaving key definition to the bean deployer. This feature might be needed if, for example, the primary key is generated by the database and is not a container-managed field in the bean class. Containers that have a tight integration with database or legacy systems that automatically generate primary keys might use this approach. It's also an attractive approach for vendors that sell shrink-wrapped beans because it makes the bean more portable. The following code shows how an entity bean using container-managed persistence defers the definition of the primary key to the deployer: // bean class for bean that uses a deferred primary key public class HypotheticalBean implements javax.ejb.EntityBean { ... public java.lang.Object ejbCreate(){ ... return null; } ... } // home interface for bean with deferred primary key public interface HypotheticalHome extends javax.ejb.EJBHome { public Hypothetical create() throws ...; public Hypothetical findByPrimaryKey(java.lang.Object key) throws ...; } Here's the relevant portion of the deployment descriptor: // primkey-field declaration for the Hypothetical bean ... <entity> <ejb-name>HypotheticalBean</ejb-name> ... <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Object</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>creationDate</field-name></cmp-field> ... </entity> Because the primary key is of type java.lang.Object, the client application's interaction with the bean's key is limited to the Object type and its methods. 10.5.3. Environment EntriesA deployment descriptor can define environment entries, which are values similar to properties that the bean can read when it is running. The bean can use environment entries to customize its behavior, find out about how it is deployed, etc. The env-entry element is used to define environment entries. This element contains a description element (optional), env-entry-name (required), env-entry-type (required), and env-entry-value (optional). Here is a typical env-entry declaration: <env-entry> <env-entry-name>minCheckNumber</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>2000</env-entry-value> </env-entry> The env-entry-name is relative to the "java:comp/env" context. For example, the minCheckNumber entry can be accessed using the path "java:comp/env/minCheckNumber" in a JNDI ENC lookup: InitialContext jndiContext = new InitialContext(); Integer miniumValue = (Integer) jndiContext.lookup("java:comp/env/minCheckNumber"); The env-entry-type can be of type String, or one of several primitive wrapper types including Integer, Long, Double, Float, Byte, Boolean, and Short. The env-entry-value is optional. The value can be specified by the bean developer or deferred to the application assembler or deployer. The subcontext "java:comp/env/ejb10-properties" can be used to make an entry available via the EJBContext.getEnvironment() method. This feature has been deprecated, but it may help you deploy EJB 1.0 beans within a EJB 1.1 server. The ejb-entry-type must always be java.lang.String for entries in this subcontext. Here's an example: <env-entry> <description>This property is available through EJBContext.getEnvironment()</description> <env-entry-name>ejb10-properties/minCheckNumber</env-entry-name> <env-entry-type>java.lang.String</env-entry-name> <env-entry-value>20000</env-entry-value> </env-entry> 10.5.4. References to Other BeansThe env-ref element is used to define references to other beans within the JNDI ENC. This makes it much easier for beans to reference other beans; they can use JNDI to look up a reference to the home interface for any beans that they are interested in. The env-ref element contains description (optional), ejb-ref-name (required), ejb-ref-type (required), remote (required), home (required), and ejb-link (optional) elements. Here is a typical env-ref declaration: <ejb-ref> <ejb-ref-name>ejb/CabinHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.titan.cabin.CabinHome</home> <remote>com.titan.cabin.Cabin</remote> </ejb-ref> The ejb-ref-name is relative to the "java:comp/env" context. It is recommended, but not required, that the name be placed under a subcontext of ejb/. Following this convention, the path used to access the Cabin bean's home would be "java:comp/env/ejb/CabinHome". The following code shows how a client bean would use this context to look up a reference to the Cabin bean: InitialContext jndiContext = new InititalContext(); Object ref = jndiContext.lookup("java:comp/env/ejb/CabinHome"); CabinHome home = (CabinHome) PortableRemoteObject.narrow(ref, CabinHome.class); The ejb-ref-type can have one of two values: Entity or Session, according to whether the bean is an entity or a session bean. The home element specifies the fully qualified class name of the bean's home interface; the remote element specifies the fully qualified class name of the bean's remote interface. If the bean referenced by the ejb-ref element is deployed in the same deployment descriptor (it is defined under the same ejb-jar element), the ejb-ref element can be linked to the bean's declaration using the ejb-link element. If, for example, the TravelAgent bean uses references to the ProcessPayment and Customer beans and they are all declared in the same deployment descriptor, then the ejb-ref elements for the TravelAgent bean can use an ejb-link element to map its ejb-ref elements to the ProcessPayment and Customer beans. The ejb-link value must match one of the ejb-name values declared in the same deployment descriptor. Here's a portion of a deployment descriptor that uses the ejb-link element: <ejb-jar> <enterprise-beans> <session> <ejb-name>TravelAgentBean</ejb-name> <remote>com.titan.travelagent.TravelAgent</remote> ... <ejb-ref> <ejb-ref-name>ejb/ProcessPaymentHome</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>com.titan.processpayment.ProcessPaymentHome</home> <remote>com.titan.processpayment.ProcessPayment</remote> <ejb-link>ProcessPaymentBean</ejb-link> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/CustomerHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.titan.customer.CustomerHome</home> <remote>com.titan.customer.Customer</remote> <ejb-link>CustomerBean</ejb-link> </ejb-ref> </session> <entity> <ejb-name>CustomerBean</ejb-name> <remote>com.titan.customer.Customer</remote> ... </entity> <session> <ejb-name>ProcessPaymentBean</ejb-name> <remote>com.titan.processpayment.ProcessPayment</remote> ... </session> ... </enterprise-beans> ... </ejb-jar> 10.5.5. References to External ResourcesBeans also use the JNDI ENC to look up external resources, like database connections, that they need to access. The mechanism for doing this is similar to the mechanism used for referencing other beans and environment entries: the external resources are mapped into a name within the JNDI ENC name space. For external resources, the mapping is performed by the resource-ref element. The resource-ref element contains description (optional), res-ref-name (required), res-type (required), and res-auth (required) elements. Here is a resource-ref declaration used for a DataSource connection factory: <resource-ref> <description>DataSource for the Titan database</description> <res-ref-name>jdbc/titanDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> The res-ref-name is relative to the "java:comp/env" context. Although it is not a requirement, it's a good idea to place connection factories under a subcontext that describes the resource type. For example:
Here is how a bean would use JNDI to look up a resource--in this case, a DataSource: InitialContext jndiContext = new InitialContext(); DataSource source = (DataSource) jndiContext.lookup("java:comp/env/jdbc/titanDB"); The res-type is used to declare the fully qualified class name of the connection factory. In this example, the res-type is javax.sql.DataSource. The res-auth tells the server who is responsible for authentication. It can have one of two values: Container or Application. If Container is specified, authentication (sign-on or login) to use the resource will be performed automatically by the container as specified at deployment time. If Application is specified, the bean itself must perform the necessary authentication before using the resource. The following code shows how a bean might sign on to a connection factory when Application is specified for res-auth: InitialContext jndiContext = new InitialContext(); DataSource source = (DataSource) jndiContext.lookup("java:comp/env/jdbc/titanDB"); String loginName = ejbContext.getCallerPrincipal().getName(); String password = ...; // get password from somewhere // use login name and password to obtain a database connection java.sql.Connection con = source.getConnection(loginName, password); 10.5.6. Security RolesThe security-role-ref element is used to define the security roles that are used by a bean and to map them into the security roles that are in effect for the runtime environment. It can contain three subelements: an optional description, a role-name (required), and an optional role-link. Here's how security roles are defined. When a role name is used in the EJBContext.isCallerInRole(String roleName) method, the role name must be statically defined (it cannot be derived at runtime) and it must be declared in the deployment descriptor using the security-role-ref element: <-- security-role-ref declaration for Account bean --> <entity> <ejb-name>AccountBean</ejb-name> ... <security-role-ref> <description> The caller must be a member of this role in order to withdraw over $10,000 </description> <role-name>Manager</role-name> <role-link>Administrator</role-link> </security-role-ref> .. </entity> The role-name defined in the deployment descriptor must match the role name used in the EJBContext.isCallerInRole() method. Here is how the role name is used in the bean's code: // Account bean uses the isCallerInRole() method public class AccountBean implements EntityBean { int id; double balance; EntityContext context; public void withdraw(Double withdraw) throws AccessDeniedException { if (withdraw.doubleValue() > 10000) { boolean isManager = context.isCallerInRole("Manager"); if (!isManager) { // only Managers can withdraw more than 10k throw new AccessDeniedException(); } } balance = balance - withdraw.doubleValue(); } ... } The role-link element is optional; it can be used to map the role name used in the bean to a logical role defined in a security-role element in the assembly-descriptor section of the deployment descriptor. If no role-link is specified, the deployer must map the security-role-ref to an existing security role in the target environment. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|