Part Six explores services.
This and subsequent chapters discuss how to address security requirements in Java EE, web, and web services applications. Every enterprise that has sensitive resources that can be accessed by many users, or resources that traverse unprotected, open, networks, such as the Internet, needs to be protected.
This chapter introduces basic security concepts and security implementation mechanisms. More information on these concepts and mechanisms can be found in the Security chapter of the Java EE 5 specification. This document is available for download online at http://www.jcp.org/en/jsr/detail?id=244.
Other chapters in this tutorial that address security requirements include the following:
Chapter 29, Securing Java EE Applications discusses adding security to Java EE components such as enterprise beans and application clients.
Chapter 30, Securing Web Applications discusses and provides examples for adding security to web components such as servlets and JSP pages.
Some of the material in this chapter assumes that you understand basic security concepts. To learn more about these concepts, you should explore the Java SE security web site before you begin this chapter. The URL for this site is http://java.sun.com/javase/6/docs/technotes/guides/security/.
This tutorial assumes deployment onto the Application Server and provides some information regarding configuration of the Application Server. The best source for information regarding configuration of the Application Server, however, is the Sun Java System Application Server 9.1 Administration Guide. The best source for development tips specific to the Application Server is the Sun Java System Application Server 9.1 Developer’s Guide. The best source for tips on deploying applications to the Application Server is the Sun Java System Application Server 9.1 Application Deployment Guide.
Java EE, web, and web services applications are made up of components that can be deployed into different containers. These components are used to build a multitier enterprise application. Security for components is provided by their containers. A container provides two kinds of security: declarative and programmatic security.
Declarative security expresses an application component’s security requirements using deployment descriptors. Deployment descriptors are external to an application, and include information that specifies how security roles and access requirements are mapped into environment-specific security roles, users, and policies. For more information about deployment descriptors, read Using Deployment Descriptors for Declarative Security.
Annotations (also called metadata) are used to specify information about security within a class file. When the application is deployed, this information can either be used by or overridden by the application deployment descriptor. Annotations save your from having to write declarative information inside XML descriptors. Instead, you just put annotations on the code and the required information gets generated. For more information about annotations, read Using Annotations.
Programmatic security is embedded in an application and is used to make security decisions. Programmatic security is useful when declarative security alone is not sufficient to express the security model of an application. For more information about programmatic security, read Using Programmatic Security.
The security behavior of a Java EE environment may be better understood by examining what happens in a simple application with a web client, a JSP user interface, and enterprise bean business logic.
In the following example, which is taken from JSR-244, the Java EE 5 Specification , the web client relies on the web server to act as its authentication proxy by collecting user authentication data from the client and using it to establish an authenticated session.
In the first step of this example, the web client requests the main application URL. This action is shown in Figure 28–1.
Since the client has not yet authenticated itself to the application environment, the server responsible for delivering the web portion of the application (hereafter referred to as web server) detects this and invokes the appropriate authentication mechanism for this resource. For more information on these mechanisms, read Security Implementation Mechanisms.
The web server returns a form that the web client uses to collect authentication data (for example, user name and password) from the user. The web client forwards the authentication data to the web server, where it is validated by the web server, as shown in Figure 28–2.
The validation mechanism may be local to a server, or it may leverage the underlying security services. On the basis of the validation, the web server sets a credential for the user.
The credential is used for future determinations of whether the user is authorized to access restricted resources it may request. The web server consults the security policy (derived from the deployment descriptor) associated with the web resource to determine the security roles that are permitted access to the resource. The web container then tests the user’s credential against each role to determine if it can map the user to the role. Figure 28–3 shows this process.
The web server’s evaluation stops with an “is authorized” outcome when the web server is able to map the user to a role. A “not authorized” outcome is reached if the web server is unable to map the user to any of the permitted roles.
If the user is authorized, the web server returns the result of the original URL request, as shown in Figure 28–4.
In our example, the response URL of a JSP page is returned, enabling the user to post form data that needs to be handled by the business logic component of the application. Read Chapter 30, Securing Web Applications for more information on protecting web applications.
The JSP page performs the remote method call to the enterprise bean, using the user’s credential to establish a secure association between the JSP page and the enterprise bean (as shown in Figure 28–5). The association is implemented as two related security contexts, one in the web server and one in the EJB container.
The EJB container is responsible for enforcing access control on the enterprise bean method. It consults the security policy (derived from the deployment descriptor) associated with the enterprise bean to determine the security roles that are permitted access to the method. For each role, the EJB container uses the security context associated with the call to determine if it can map the caller to the role.
The container’s evaluation stops with an “is authorized” outcome when the container is able to map the caller’s credential to a role. A “not authorized” outcome is reached if the container is unable to map the caller to any of the permitted roles. A “not authorized” result causes an exception to be thrown by the container, and propagated back to the calling JSP page.
If the call is authorized, the container dispatches control to the enterprise bean method. The result of the bean’s execution of the call is returned to the JSP, and ultimately to the user by the web server and the web client.
Read Chapter 29, Securing Java EE Applications for more information on protecting web applications.
A properly implemented security mechanism will provide the following functionality:
Prevent unauthorized access to application functions and business or personal data
Hold system users accountable for operations they perform (non-repudiation)
Protect a system from service interruptions and other breaches that affect quality of service
Ideally, properly implemented security mechanisms will also provide the following functionality:
Easy to administer
Transparent to system users
Interoperable across application and enterprise boundaries
Java EE applications consist of components that can contain both protected and unprotected resources. Often, you need to protect resources to ensure that only authorized users have access. Authorization provides controlled access to protected resources. Authorization is based on identification and authentication. Identification is a process that enables recognition of an entity by a system, and authentication is a process that verifies the identity of a user, device, or other entity in a computer system, usually as a prerequisite to allowing access to resources in a system.
Authorization and authentication are not required for an entity to access unprotected resources. Accessing a resource without authentication is referred to as unauthenticated or anonymous access.
These and several other well-defined characteristics of application security that, when properly addressed, help to minimize the security threats faced by an enterprise, include the following:
Authentication: The means by which communicating entities (for example, client and server) prove to one another that they are acting on behalf of specific identities that are authorized for access. This ensures that users are who they say they are.
Authorization, or Access Control: The means by which interactions with resources are limited to collections of users or programs for the purpose of enforcing integrity, confidentiality, or availability constraints. This ensures that users have permission to perform operations or access data.
Data integrity: The means used to prove that information has not been modified by a third party (some entity other than the source of the information). For example, a recipient of data sent over an open network must be able to detect and discard messages that were modified after they were sent. This ensures that only authorized users can modify data.
Confidentiality or Data Privacy: The means used to ensure that information is made available only to users who are authorized to access it. This ensures that only authorized users can view sensitive data.
Non-repudiation: The means used to prove that a user performed some action such that the user cannot reasonably deny having done so. This ensures that transactions can be proven to have happened.
Quality of Service (QoS): The means used to provide better service to selected network traffic over various technologies.
Auditing: The means used to capture a tamper-resistant record of security-related events for the purpose of being able to evaluate the effectiveness of security policies and mechanisms. To enable this, the system maintains a record of transactions and security information.
The characteristics of an application should be considered when deciding the layer and type of security to be provided for applications. The following sections discuss the characteristics of the common mechanisms that can be used to secure Java EE applications. Each of these mechanisms can be used individually or with others to provide protection layers based on the specific needs of your implementation.
Java SE provides support for a variety of security features and mechanisms, including:
Java Authentication and Authorization Service (JAAS): JAAS is a set of APIs that enable services to authenticate and enforce access controls upon users. JAAS provides a pluggable and extensible framework for programmatic user authentication and authorization. JAAS is a core Java SE API and is an underlying technology for Java EE security mechanisms.
Java Generic Security Services (Java GSS-API): Java GSS-API is a token-based API used to securely exchange messages between communicating applications. The GSS-API offers application programmers uniform access to security services atop a variety of underlying security mechanisms, including Kerberos.
Java Cryptography Extension (JCE): JCE provides a framework and implementations for encryption, key generation and key agreement, and Message Authentication Code (MAC) algorithms. Support for encryption includes symmetric, asymmetric, block, and stream ciphers. Block ciphers operate on groups of bytes while stream ciphers operate on one byte at a time. The software also supports secure streams and sealed objects.
Java Secure Sockets Extension (JSSE): JSSE provides a framework and an implementation for a Java version of the SSL and TLS protocols and includes functionality for data encryption, server authentication, message integrity, and optional client authentication to enable secure Internet communications.
Simple Authentication and Security Layer (SASL): SASL is an Internet standard (RFC 2222) that specifies a protocol for authentication and optional establishment of a security layer between client and server applications. SASL defines how authentication data is to be exchanged but does not itself specify the contents of that data. It is a framework into which specific authentication mechanisms that specify the contents and semantics of the authentication data can fit.
Java SE also provides a set of tools for managing keystores, certificates, and policy files; generating and verifying JAR signatures; and obtaining, listing, and managing Kerberos tickets.
For more information on Java SE security, visit its web page at http://java.sun.com/javase/6/docs/technotes/guides/security/.
Java EE security services are provided by the component container and can be implemented using declarative or programmatic techniques (container security is discussed more in Securing Containers). Java EE security services provide a robust and easily configured security mechanism for authenticating users and authorizing access to application functions and associated data at many different layers. Java EE security services are separate from the security mechanisms of the operating system.
In Java EE, component containers are responsible for providing application-layer security. Application-layer security provides security services for a specific application type tailored to the needs of the application. At the application layer, application firewalls can be employed to enhance application protection by protecting the communication stream and all associated application resources from attacks.
Java EE security is easy to implement and configure, and can offer fine-grained access control to application functions and data. However, as is inherent to security applied at the application layer, security properties are not transferable to applications running in other environments and only protect data while it is residing in the application environment. In the context of a traditional application, this is not necessarily a problem, but when applied to a web services application, where data often travels across several intermediaries, you would need to use the Java EE security mechanisms along with transport-layer security and message-layer security for a complete security solution.
The advantages of using application-layer security include the following:
Security is uniquely suited to the needs of the application.
Security is fine-grained, with application-specific settings.
The disadvantages of using application-layer security include the following:
The application is dependent on security attributes that are not transferable between application types.
Support for multiple protocols makes this type of security vulnerable.
Data is close to or contained within the point of vulnerability.
For more information on providing security at the application layer, read Securing Containers.
Transport-layer security is provided by the transport mechanisms used to transmit information over the wire between clients and providers, thus transport-layer security relies on secure HTTP transport (HTTPS) using Secure Sockets Layer (SSL). Transport security is a point-to-point security mechanism that can be used for authentication, message integrity, and confidentiality. When running over an SSL-protected session, the server and client can authenticate one another and negotiate an encryption algorithm and cryptographic keys before the application protocol transmits or receives its first byte of data. Security is “live” from the time it leaves the consumer until it arrives at the provider, or vice versa, even across intermediaries. The problem is that it is not protected once it gets to its destination. One solution is to encrypt the message before sending.
Transport-layer security is performed in a series of phases, which are listed here:
The client and server agree on an appropriate algorithm.
A key is exchanged using public-key encryption and certificate-based authentication.
A symmetric cipher is used during the information exchange.
Digital certificates are necessary when running secure HTTP transport (HTTPS) using Secure Sockets Layer (SSL). The HTTPS service of most web servers will not run unless a digital certificate has been installed. Digital certificates have already been created for the Application Server. If you are using a different server, use the procedure outlined in Working with Digital Certificates to set up a digital certificate that can be used by your web or application server to enable SSL.
The advantages of using transport-layer security include the following:
Relatively simple, well understood, standard technology.
Applies to message body and attachments.
The disadvantages of using transport-layer security include the following:
Tightly-coupled with transport-layer protocol.
All or nothing approach to security. This implies that the security mechanism is unaware of message contents, and as such, you cannot selectively apply security to portions of the message as you can with message-layer security.
Protection is transient. The message is only protected while in transit. Protection is removed automatically by the endpoint when it receives the message.
Not an end-to-end solution, simply point-to-point.
For more information on transport-layer security, read Establishing a Secure Connection Using SSL.
In message-layer security, security information is contained within the SOAP message and/or SOAP message attachment, which allows security information to travel along with the message or attachment. For example, a portion of the message may be signed by a sender and encrypted for a particular receiver. When the message is sent from the initial sender, it may pass through intermediate nodes before reaching its intended receiver. In this scenario, the encrypted portions continue to be opaque to any intermediate nodes and can only be decrypted by the intended receiver. For this reason, message-layer security is also sometimes referred to as end-to-end security.
The advantages of message-layer security include the following:
Security stays with the message over all hops and after the message arrives at its destination.
Security can be selectively applied to different portions of a message (and to attachments if using XWSS).
Message security can be used with intermediaries over multiple hops.
Message security is independent of the application environment or transport protocol.
The disadvantage of using message-layer security is that it is relatively complex and adds some overhead to processing.
The Application Server supports message security. It uses Web Services Security (WSS) to secure messages. Because this message security is specific to the Application Server and not a part of the Java EE platform, this tutorial does not discuss using WSS to secure messages. See the Sun Java System Application Server 9.1 Administration Guide and Sun Java System Application Server 9.1 Developer’s Guide for more information.
In Java EE, the component containers are responsible for providing application security. A container provides two types of security: declarative and programmatic. The following sections discuss these concepts in more detail.
Declarative security expresses an application component’s security requirements using deployment descriptors. A deployment descriptor is an XML document with an .xml extension that describes the deployment settings of an application, a module, or a component. Because deployment descriptor information is declarative, it can be changed without the need to modify the source code. At runtime, the Java EE server reads the deployment descriptor and acts upon the application, module, or component accordingly.
This tutorial does not document how to write the deployment descriptors from scratch, only what configurations each example requires its deployment descriptors to define. For help with writing deployment descriptors, you can view the provided deployment descriptors in a text editor. Each example’s deployment descriptors are stored at the top layer of each example’s directory. Another way to learn how to write deployment descriptors is to read the specification in which the deployment descriptor elements are defined.
Deployment descriptors must provide certain structural information for each component if this information has not been provided in annotations or is not to be defaulted.
Different types of components use different formats, or schema, for their deployment descriptors. The security elements of deployment descriptors which are discussed in this tutorial include the following:
Enterprise JavaBeans components use an EJB deployment descriptor that must be named META-INF/ejb-jar.xml and must be contained in the EJB JAR file.
The schema for enterprise bean deployment descriptors is provided in the EJB 3.0 Specification (JSR-220), Chapter 18.5, Deployment Descriptor XML Schema, which can be downloaded from http://jcp.org/en/jsr/detail?id=220.
Security elements for EJB deployment descriptors are discussed in this tutorial in the section Using Enterprise Bean Security Deployment Descriptor Elements.
Web Services components use a jaxrpc-mapping-info deployment descriptor defined in JSR 109. This deployment descriptor provides deployment-time mapping functionality between Java and WSDL. In conjunction with JSR 181, JAX-WS 2.0 complements this mapping functionality with development-time Java annotations that control mapping between Java and WSDL.
The schema for web services deployment descriptors is provided in Web Services for Java EE (JSR-109), section 7.1, Web Services Deployment Descriptor XML Schema, which can be downloaded from http://jcp.org/en/jsr/detail?id=109.
Schema elements for web application deployment descriptors are discussed in this tutorial in the section Declaring Security Requirements in a Deployment Descriptor.
Web components use a web application deployment descriptor named web.xml.
The schema for web component deployment descriptors is provided in the Java Servlet 2.5 Specification (JSR-154), section SRV.13, Deployment Descriptor, which can be downloaded from http://jcp.org/en/jsr/detail?id=154.
Security elements for web application deployment descriptors are discussed in this tutorial in the section Declaring Security Requirements in a Deployment Descriptor.
Annotations enable a declarative style of programming, and so encompass both the declarative and programmatic security concepts. Users can specify information about security within a class file using annotations. When the application is deployed, this information is used by the Application Server. Not all security information can be specified using annotations, however. Some information must be specified in the application deployment descriptors.
Annotations let you avoid writing boilerplate code under many circumstances by enabling tools to generate it from annotations in the source code. This leads to a declarative programming style, where the programmer says what should be done and tools emit the code to do it. It also eliminates the need for maintaining side files that must be kept up to date with changes in source files. Instead the information can be maintained in the source file.
In this tutorial, specific annotations that can be used to specify security information within a class file are described in the following sections:
The following are sources for more information on annotations:
JSR 175: A Metadata Facility for the Java Programming Language
JSR 181: Web Services Metadata for the Java Platform
JSR 250: Common Annotations for the Java Platform
The Java SE discussion of annotations
Links to this information are provided in Further Information about Security.
Programmatic security is embedded in an application and is used to make security decisions. Programmatic security is useful when declarative security alone is not sufficient to express the security model of an application. The API for programmatic security consists of two methods of the EJBContext interface and two methods of the servlet HttpServletRequest interface. These methods allow components to make business logic decisions based on the security role of the caller or remote user.
Programmatic security is discussed in more detail in the following sections:
This tutorial describes deployment to the Application Server, which provides highly secure, interoperable, and distributed component computing based on the Java EE security model. The Application Server supports the Java EE 5 security model. You can configure the Application Server for the following purposes:
Adding, deleting, or modifying authorized users. For more information on this topic, read Working with Realms, Users, Groups, and Roles.
Configuring secure JMX connectors.
Defining an interface for pluggable authorization providers using Java Authorization Contract for Containers (JACC).
Java Authorization Contract for Containers (JACC) defines security contracts between the Application Server and authorization policy modules. These contracts specify how the authorization providers are installed, configured, and used in access decisions.
The following features are specific to the Application Server:
For more information about configuring the Application Server, read the Sun Java System Application Server 9.1 Developer’s Guide and Sun Java System Application Server 9.1 Administration Guide.
You often need to protect resources to ensure that only authorized users have access. Authorization provides controlled access to protected resources. Authorization is based on identification and authentication. Identification is a process that enables recognition of an entity by a system, and authentication is a process that verifies the identity of a user, device, or other entity in a computer system, usually as a prerequisite to allowing access to resources in a system. These concepts are discussed in more detail in Characteristics of Application Security.
This section discusses setting up users so that they can be correctly identified and either given access to protected resources, or denied access if the user is not authorized to access the protected resources. To authenticate a user, you need to follow these basic steps:
The Application Developer writes code to prompt the user for their user name and password. The different methods of authentication are discussed in Specifying an Authentication Mechanism.
The Application Developer communicates how to set up security for the deployed application by use of a deployment descriptor. This step is discussed in Setting Up Security Roles.
The Server Administrator sets up authorized users and groups on the Application Server. This is discussed in Managing Users and Groups on the Application Server.
The Application Deployer maps the application’s security roles to users, groups, and principals defined on the Application Server. This topic is discussed in Mapping Roles to Users and Groups.
A realm is defined on a web or application server. It contains a collection of users, which may or may not be assigned to a group, that are controlled by the same authentication policy. Managing users on the Application Server is discussed in Managing Users and Groups on the Application Server.
An application will often prompt a user for their user name and password before allowing access to a protected resource. After the user has entered their user name and password, that information is passed to the server, which either authenticates the user and sends the protected resource, or does not authenticate the user, in which case access to the protected resource is denied. This type of user authentication is discussed in Specifying an Authentication Mechanism.
In some applications, authorized users are assigned to roles. In this situation, the role assigned to the user in the application must be mapped to a group defined on the application server. Figure 28–6 shows this. More information on mapping roles to users and groups can be found in Setting Up Security Roles.
The following sections provide more information on realms, users, groups, and roles.
For a web application, a realm is a complete database of users and groups that identify valid users of a web application (or a set of web applications) and are controlled by the same authentication policy.
The Java EE server authentication service can govern users in multiple realms. In this release of the Application Server, the file, admin-realm, and certificate realms come preconfigured for the Application Server.
In the file realm, the server stores user credentials locally in a file named keyfile. You can use the Admin Console to manage users in the file realm.
When using the file realm, the server authentication service verifies user identity by checking the file realm. This realm is used for the authentication of all clients except for web browser clients that use the HTTPS protocol and certificates.
In the certificate realm, the server stores user credentials in a certificate database. When using the certificate realm, the server uses certificates with the HTTPS protocol to authenticate web clients. To verify the identity of a user in the certificate realm, the authentication service verifies an X.509 certificate. For step-by-step instructions for creating this type of certificate, see Working with Digital Certificates. The common name field of the X.509 certificate is used as the principal name.
The admin-realm is also a FileRealm and stores administrator user credentials locally in a file named admin-keyfile. You can use the Admin Console to manage users in this realm in the same way you manage users in the file realm. For more information, see Managing Users and Groups on the Application Server.
A user is an individual (or application program) identity that has been defined in the Application Server. In a web application, a user can have a set of roles associated with that identity, which entitles them to access all resources protected by those roles. Users can be associated with a group.
A Java EE user is similar to an operating system user. Typically, both types of users represent people. However, these two types of users are not the same. The Java EE server authentication service has no knowledge of the user name and password you provide when you log on to the operating system. The Java EE server authentication service is not connected to the security mechanism of the operating system. The two security services manage users that belong to different realms.
A group is a set of authenticated users, classified by common traits, defined in the Application Server.
A Java EE user of the file realm can belong to an Application Server group. (A user in the certificate realm cannot.) An Application Server group is a category of users classified by common traits, such as job title or customer profile. For example, most customers of an e-commerce application might belong to the CUSTOMER group, but the big spenders would belong to the PREFERRED group. Categorizing users into groups makes it easier to control the access of large numbers of users.
An Application Server group has a different scope from a role. An Application Server group is designated for the entire Application Server, whereas a role is associated only with a specific application in the Application Server.
A role is an abstract name for the permission to access a particular set of resources in an application. A role can be compared to a key that can open a lock. Many people might have a copy of the key. The lock doesn’t care who you are, only that you have the right key.
The following terminology is also used to describe the security requirements of the Java EE platform:
Principal: A principal is an entity that can be authenticated by an authentication protocol in a security service that is deployed in an enterprise. A principal is identified using a principal name and authenticated using authentication data.
Security policy domain (also known as security domain or realm): A security policy domain is a scope over which a common security policy is defined and enforced by the security administrator of the security service.
Security attributes: A set of security attributes is associated with every principal. The security attributes have many uses, for example, access to protected resources and auditing of users. Security attributes can be associated with a principal by an authentication protocol.
Credential: A credential contains or references information (security attributes) used to authenticate a principal for Java EE product services. A principal acquires a credential upon authentication, or from another principal that allows its credential to be used.
Managing users on the Application Server is discussed in more detail in the Sun Java System Application Server 9.1 Administration Guide.
This tutorial provides steps for managing users that will need to be completed to work through the tutorial examples.
To add users to the Application Server, follow these steps:
Start the Application Server if you haven’t already done so. Information on starting the Application Server is available in Starting and Stopping the Application Server.
Start the Admin Console if you haven’t already done so. You can start the Admin Console by starting a web browser and entering the URL http://localhost:4848/asadmin. If you changed the default Admin port during installation, enter the correct port number in place of 4848.
To log in to the Admin Console, enter the user name and password of a user in the admin-realm who belongs to the asadmin group. The name and password entered during installation will work, as will any users added to this realm and group subsequent to installation.
Expand the Configuration node in the Admin Console tree.
Expand the Security node in the Admin Console tree.
Expand the Realms node.
Select the file realm to add users you want to enable to access applications running in this realm. (For the example security applications, select the file realm.)
Select the admin-realm to add users you want to enable as system administrators of the Application Server.
You cannot enter users into the certificate realm using the Admin Console. You can only add certificates to the certificate realm. For information on adding (importing) certificates to the certificate realm, read Adding Users to the Certificate Realm.
Click the Manage Users button.
Click New to add a new user to the realm.
Enter the correct information into the User ID, Password, and Group(s) fields.
If you are adding a user to the file realm, enter the name to identify the user, a password to allow the user access to the realm, and a group to which this user belongs. For more information on these properties, read Working with Realms, Users, Groups, and Roles.
For the example security applications, enter a user with any name and password you like, but make sure that the user is assigned to the group of user.
If you are adding a user to the admin-realm, enter the name to identify the user, a password to allow the user access to the Application Server, and enter asadmin in the Group field.
Click OK to add this user to the list of users in the realm.
Click Logout when you have completed this task.
In the certificate realm, user identity is set up in the Application Server security context and populated with user data obtained from cryptographically-verified client certificates. For step-by-step instructions for creating this type of certificate, see Working with Digital Certificates.
When you design an enterprise bean or web component, you should always think about the kinds of users who will access the component. For example, a web application for a human resources department might have a different request URL for someone who has been assigned the role of DEPT_ADMIN than for someone who has been assigned the role of DIRECTOR. The DEPT_ADMIN role may let you view employee data, but the DIRECTOR role enables you to modify employee data, including salary data. Each of these security roles is an abstract logical grouping of users that is defined by the person who assembles the application. When an application is deployed, the deployer will map the roles to security identities in the operational environment, as shown in Figure 28–6.
For applications, you define security roles in the Java EE deployment descriptor file application.xml, and the corresponding role mappings in the Application Server deployment descriptor file sun-application.xml. For individually deployed web or EJB modules, you define roles in the Java EE deployment descriptor files web.xml or ejb-jar.xml and the corresponding role mappings in the Application Server deployment descriptor files sun-web.xml or sun-ejb-jar.xml.
The following is an example of a security constraint from a web.xml application deployment descriptor file where the role of DEPT-ADMIN is authorized for methods that review employee data and the role of DIRECTOR is authorized for methods that change employee data.
<security-constraint> <web-resource-collection> <web-resource-name>view dept data</web-resource-name> <url-pattern>/hr/employee/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>DEPT_ADMIN</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> <security-constraint> <web-resource-collection> <web-resource-name>change dept data</web-resource-name> <url-pattern>/hr/employee/*</url-pattern> <http-method>GET</http-method> <http-method>PUT</http-method> </web-resource-collection> <auth-constraint> <role-name>DIRECTOR</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>
The web.xml application deployment descriptor is described in more detail in Declaring Security Requirements in a Deployment Descriptor.
After users have provided their login information, and the application has declared what roles are authorized to access protected parts of an application, the next step is to map the security role to the name of a user, or principal. This step is discussed in the following section.
When you are developing a Java EE application, you don’t need to know what categories of users have been defined for the realm in which the application will be run. In the Java EE platform, the security architecture provides a mechanism for mapping the roles defined in the application to the users or groups defined in the runtime realm. To map a role name permitted by the application or module to principals (users) and groups defined on the server, use the security-role-mapping element in the runtime deployment descriptor (sun-application.xml, sun-web.xml, or sun-ejb-jar.xml) file. The entry needs to declare a mapping between a security role used in the application and one or more groups or principals defined for the applicable realm of the Application Server. An example for the sun-web.xml file is shown below:
<sun-web-app> <security-role-mapping> <role-name>DIRECTOR</role-name> <principal-name>mcneely</principal-name> </security-role-mapping> <security-role-mapping> <role-name>MANAGER</role-name> <group-name>manager</group-name> </security-role-mapping> </sun-web-app>
The role name can be mapped to either a specific principal (user), a group, or both. The principal or group names referenced must be valid principals or groups in the current default realm of the Application Server. The role-name in this example must exactly match the role-name in the security-role element of the corresponding web.xml file or the role name defined in the @DeclareRoles or @RolesAllowed annotations.
Sometimes the role names used in the application are the same as the group names defined on the Application Server. Under these circumstances, you can enable a default principal-to-role mapping on the Application Server using the Admin Console. From the Admin Console, select Configuration, then Security, then check the enable box beside Default Principal to Role Mapping. If you need more information about using the Admin Console, see Adding Users to the Application Server.
Secure Socket Layer (SSL) technology is security that is implemented at the transport layer (see Transport-Layer Security, for more information about transport layer security). SSL allows web browsers and web servers to communicate over a secure connection. In this secure connection, the data that is being sent is encrypted before being sent and then is decrypted upon receipt and before processing. Both the browser and the server encrypt all traffic before sending any data. SSL addresses the following important security considerations.
Authentication: During your initial attempt to communicate with a web server over a secure connection, that server will present your web browser with a set of credentials in the form of a server certificate. The purpose of the certificate is to verify that the site is who and what it claims to be. In some cases, the server may request a certificate that the client is who and what it claims to be (which is known as client authentication).
Confidentiality: When data is being passed between the client and the server on a network, third parties can view and intercept this data. SSL responses are encrypted so that the data cannot be deciphered by the third party and the data remains confidential.
Integrity: When data is being passed between the client and the server on a network, third parties can view and intercept this data. SSL helps guarantee that the data will not be modified in transit by that third party.
An SSL HTTPS connector is already enabled in the Application Server. For more information on configuring SSL for the Application Server, refer to the Sun Java System Application Server 9.1 Administration Guide.
If you are using a different application server or web server, an SSL HTTPS connector might or might not be enabled. If you are using a server that needs its SSL connector to be configured, consult the documentation for that server.
As a general rule, to enable SSL for a server, you must address the following issues:
There must be a Connector element for an SSL connector in the server deployment descriptor.
There must be valid keystore and certificate files.
The location of the keystore file and its password must be specified in the server deployment descriptor.
You can verify whether or not SSL is enabled by following the steps in Verifying SSL Support.
To specify a requirement that protected resources be received over a protected transport layer connection (SSL), specify a user data constraint in the application deployment descriptor. The following is an example of a web.xml application deployment descriptor that specifies that SSL be used:
<security-constraint> <web-resource-collection> <web-resource-name>view dept data</web-resource-name> <url-pattern>/hr/employee/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>DEPT_ADMIN</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>
A user data constraint (<user-data-constraint> in the deployment descriptor) requires that all constrained URL patterns and HTTP methods specified in the security constraint are received over a protected transport layer connection such as HTTPS (HTTP over SSL). A user data constraint specifies a transport guarantee (<transport-guarantee> in the deployment descriptor). The choices for transport guarantee include CONFIDENTIAL, INTEGRAL, or NONE. If you specify CONFIDENTIAL or INTEGRAL as a security constraint, that type of security constraint applies to all requests that match the URL patterns in the web resource collection and not just to the login dialog box.
The strength of the required protection is defined by the value of the transport guarantee.
Specify CONFIDENTIAL when the application requires that data be transmitted so as to prevent other entities from observing the contents of the transmission.
Specify INTEGRAL when the application requires that the data be sent between client and server in such a way that it cannot be changed in transit.
Specify NONE to indicate that the container must accept the constrained requests on any connection, including an unprotected one.
The user data constraint is handy to use with basic and form-based user authentication. When the login authentication method is set to BASIC or FORM, passwords are not protected, meaning that passwords sent between a client and a server on an unprotected session can be viewed and intercepted by third parties. Using a user data constraint with the user authentication mechanism can alleviate this concern. Configuring a user authentication mechanism is described in Specifying an Authentication Mechanism.
For testing purposes, and to verify that SSL support has been correctly installed, load the default introduction page with a URL that connects to the port defined in the server deployment descriptor:
https://localhost:8181/
The https in this URL indicates that the browser should be using the SSL protocol. The localhost in this example assumes that you are running the example on your local machine as part of the development process. The 8181 in this example is the secure port that was specified where the SSL connector was created. If you are using a different server or port, modify this value accordingly.
The first time that you load this application, the New Site Certificate or Security Alert dialog box displays. Select Next to move through the series of dialog boxes, and select Finish when you reach the last dialog box. The certificates will display only the first time. When you accept the certificates, subsequent hits to this site assume that you still trust the content.
The SSL protocol is designed to be as efficient as securely possible. However, encryption and decryption are computationally expensive processes from a performance standpoint. It is not strictly necessary to run an entire web application over SSL, and it is customary for a developer to decide which pages require a secure connection and which do not. Pages that might require a secure connection include login pages, personal information pages, shopping cart checkouts, or any pages where credit card information could possibly be transmitted. Any page within an application can be requested over a secure socket by simply prefixing the address with https: instead of http:. Any pages that absolutely require a secure connection should check the protocol type associated with the page request and take the appropriate action if https is not specified.
Using name-based virtual hosts on a secured connection can be problematic. This is a design limitation of the SSL protocol itself. The SSL handshake, where the client browser accepts the server certificate, must occur before the HTTP request is accessed. As a result, the request information containing the virtual host name cannot be determined before authentication, and it is therefore not possible to assign multiple certificates to a single IP address. If all virtual hosts on a single IP address need to authenticate against the same certificate, the addition of multiple virtual hosts should not interfere with normal SSL operations on the server. Be aware, however, that most client browsers will compare the server’s domain name against the domain name listed in the certificate, if any (this is applicable primarily to official, CA-signed certificates). If the domain names do not match, these browsers will display a warning to the client. In general, only address-based virtual hosts are commonly used with SSL in a production environment.
Digital certificates for the Application Server have already been generated and can be found in the directory domain-dir/config/. These digital certificates are self-signed and are intended for use in a development environment; they are not intended for production purposes. For production purposes, generate your own certificates and have them signed by a CA.
The instructions in this section apply to the developer and cluster profiles of the Application Server. In the enterprise profile, the certutil utility is used to create digital certificates. For more information, see the Sun Java System Application Server 9.1 Administration Guide.
To use SSL, an application or web server must have an associated certificate for each external interface, or IP address, that accepts secure connections. The theory behind this design is that a server should provide some kind of reasonable assurance that its owner is who you think it is, particularly before receiving any sensitive information. It may be useful to think of a certificate as a “digital driver’s license” for an Internet address. It states with which company the site is associated, along with some basic contact information about the site owner or administrator.
The digital certificate is cryptographically signed by its owner and is difficult for anyone else to forge. For sites involved in e-commerce or in any other business transaction in which authentication of identity is important, a certificate can be purchased from a well-known certificate authority (CA) such as VeriSign or Thawte. If your server certificate is self-signed, you must install it in the Application Server keystore file (keystore.jks). If your client certificate is self-signed, you should install it in the Application Server truststore file (cacerts.jks).
Sometimes authentication is not really a concern. For example, an administrator might simply want to ensure that data being transmitted and received by the server is private and cannot be snooped by anyone eavesdropping on the connection. In such cases, you can save the time and expense involved in obtaining a CA certificate and simply use a self-signed certificate.
SSL uses public key cryptography, which is based on key pairs. Key pairs contain one public key and one private key. If data is encrypted with one key, it can be decrypted only with the other key of the pair. This property is fundamental to establishing trust and privacy in transactions. For example, using SSL, the server computes a value and encrypts the value using its private key. The encrypted value is called a digital signature. The client decrypts the encrypted value using the server’s public key and compares the value to its own computed value. If the two values match, the client can trust that the signature is authentic, because only the private key could have been used to produce such a signature.
Digital certificates are used with the HTTPS protocol to authenticate web clients. The HTTPS service of most web servers will not run unless a digital certificate has been installed. Use the procedure outlined in the next section, Creating a Server Certificate, to set up a digital certificate that can be used by your application or web server to enable SSL.
One tool that can be used to set up a digital certificate is keytool, a key and certificate management utility that ships with the Java SE SDK. It enables users to administer their own public/private key pairs and associated certificates for use in self-authentication (where the user authenticates himself or herself to other users or services) or data integrity and authentication services, using digital signatures. It also allows users to cache the public keys (in the form of certificates) of their communicating peers. For a better understanding of keytool and public key cryptography, read the keytool documentation at http://java.sun.com/javase/6/docs/technotes/tools/solaris/keytool.html.
A server certificate has already been created for the Application Server. The certificate can be found in the domain-dir/config/ directory. The server certificate is in keystore.jks. The cacerts.jks file contains all the trusted certificates, including client certificates.
If necessary, you can use keytool to generate certificates. The keytool utility stores the keys and certificates in a file termed a keystore, a repository of certificates used for identifying a client or a server. Typically, a keystore is a file that contains one client or one server’s identity. It protects private keys by using a password.
If you don’t specify a directory when specifying the keystore file name, the keystores are created in the directory from which the keytool command is run. This can be the directory where the application resides, or it can be a directory common to many applications.
To create a server certificate, follow these steps:
Create the keystore.
Export the certificate from the keystore.
Sign the certificate.
Import the certificate into a truststore: a repository of certificates used for verifying the certificates. A truststore typically contains more than one certificate.
Run keytool to generate the server keystore, keystore.jks. This step uses the alias server-alias to generate a new public/private key pair and wrap the public key into a self-signed certificate inside keystore.jks. The key pair is generated using an algorithm of type RSA, with a default password of changeit. For more information on keytool options, see its online help at http://java.sun.com/javase/6/docs/technotes/tools/solaris/keytool.html.
RSA is public-key encryption technology developed by RSA Data Security, Inc. The acronym stands for Rivest, Shamir, and Adelman, the inventors of the technology.
From the directory in which you want to create the keystore, run keytool with the following parameters.
Generate the server certificate. (Type the keytool command all on one line.)
java-home\bin\keytool -genkey -alias server-alias-keyalg RSA -keypass changeit -storepass changeit -keystore keystore.jks |
When you press Enter, keytool prompts you to enter the server name, organizational unit, organization, locality, state, and country code.
You must enter the server name in response to keytool’s first prompt, in which it asks for first and last names. For testing purposes, this can be localhost.
When you run the example applications, the host specified in the keystore must match the host identified in the javaee.server.name property specified in the file tut-install/javaeetutorial5/examples/bp-project/build.properties.
Export the generated server certificate in keystore.jks into the file server.cer. (Type the keytool all on one line.)
java-home\bin\keytool -export -alias server-alias -storepass changeit -file server.cer -keystore keystore.jks |
If you want to have the certificate signed by a CA, read Signing Digital Certificates for more information.
To create the truststore file cacerts.jks and add the server certificate to the truststore, run keytool from the directory where you created the keystore and server certificate. Use the following parameters:
java-home\bin\keytool -import -v -trustcacerts -alias server-alias -file server.cer -keystore cacerts.jks -keypass changeit -storepass changeit |
Information on the certificate, such as that shown next, will display.
% keytool -import -v -trustcacerts -alias server-alias -file server.cer -keystore cacerts.jks -keypass changeit -storepass changeit Owner: CN=localhost, OU=Sun Micro, O=Docs, L=Santa Clara, ST=CA, C=USIssuer: CN=localhost, OU=Sun Micro, O=Docs, L=Santa Clara, ST=CA, C=USSerial number: 3e932169Valid from: Tue Apr 08Certificate fingerprints:MD5: 52:9F:49:68:ED:78:6F:39:87:F3:98:B3:6A:6B:0F:90 SHA1: EE:2E:2A:A6:9E:03:9A:3A:1C:17:4A:28:5E:97:20:78:3F: Trust this certificate? [no]: |
Enter yes, and then press the Enter or Return key. The following information displays:
Certificate was added to keystore[Saving cacerts.jks] |
After you’ve created a digital certificate, you will want to have it signed by its owner. After the digital certificate has been cryptographically signed by its owner, it is difficult for anyone else to forge. For sites involved in e-commerce or any other business transaction in which authentication of identity is important, a certificate can be purchased from a well-known certificate authority such as VeriSign or Thawte.
As mentioned earlier, if authentication is not really a concern, you can save the time and expense involved in obtaining a CA certificate and simply use the self-signed certificate.
This example assumes that the keystore is named keystore.jks, the certificate file is server.cer, and the CA file is cacerts.jks. To get your certificate digitally signed by a CA:
Generate a Certificate Signing Request (CSR).
keytool -certreq -alias server-alias -keyalg RSA -file csr-filename -keystore cacerts.jks |
Send the contents of the csr-filename for signing.
If you are using Verisign CA, go to http://digitalid.verisign.com/. Verisign will send the signed certificate in email. Store this certificate in a file.
Follow the steps in Creating a Server Certificate, to create your own server certificate, have it signed by a CA, and import the certificate into keystore.jks.
Make sure that when you create the certificate, you follow these rules:
When you create the server certificate, keytool prompts you to enter your first and last name. In response to this prompt, you must enter the name of your server. For testing purposes, this can be localhost.
The server/host specified in the keystore must match the host identified in the javaee.server.name property specified in the tut-install/javaeetutorial5/examples/bp-project/build.properties file for running the example applications.
Your key/certificate password in keystore.jks should match the password of your keystore, keystore.jks. This is a bug. If there is a mismatch, the Java SDK cannot read the certificate and you get a “tampered” message.
If you want to replace the existing keystore.jks, you must either change your keystore’s password to the default password (changeit) or change the default password to your keystore’s password.
To specify that the Application Server should use the new keystore for authentication and authorization decisions, you must set the JVM options for the Application Server so that they recognize the new keystore. To use a different keystore than the one provided for development purposes, follow these steps.
Start the Application Server if you haven’t already done so. Information on starting the Application Server can be found in Starting and Stopping the Application Server.
Start the Admin Console. Information on starting the Admin Console can be found in Starting the Admin Console.
Select Application Server in the Admin Console tree.
Select the JVM Settings tab.
Select the JVM Options tab.
Change the following JVM options so that they point to the location and name of the new keystore. There current settings are shown below:
-Djavax.net.ssl.keyStore=${com.sun.aas.instanceRoot}/config/keystore.jks -Djavax.net.ssl.trustStore=${com.sun.aas.instanceRoot}/config/cacerts.jks |
If you’ve changed the keystore password from its default value, you need to add the password option as well:
-Djavax.net.ssl.keyStorePassword=your-new-password |
Log out of the Admin Console and restart the Application Server.
To check the contents of a keystore that contains a certificate with an alias server-alias, use this command:
keytool -list -keystore keystore.jks -alias server-alias -v |
To check the contents of the cacerts file, use this command:
keytool -list -keystore cacerts.jks |
This section discusses setting up client-side authentication. When both server-side and client-side authentication are enabled, it is called mutual, or two-way, authentication. In client authentication, clients are required to submit certificates that are issued by a certificate authority that you choose to accept.
There are at least two ways to enable mutual authentication over SSL:
The preferred method is to set the method of authentication in the web.xml application deployment descriptor to CLIENT-CERT. This enforces mutual authentication by modifying the deployment descriptor of the given application. By enabling client authentication in this way, client authentication is enabled only for a specific resource controlled by the security constraint, and the check is only performed when the application requires client authentication.
A less commonly used method is to set the clientAuth property in the certificate realm to true if you want the SSL stack to require a valid certificate chain from the client before accepting a connection. A false value (which is the default) will not require a certificate chain unless the client requests a resource protected by a security constraint that uses CLIENT-CERT authentication. When you enable client authentication by setting the clientAuth property to true, client authentication will be required for all the requests going through the specified SSL port. If you turn clientAuth on, it is on all of the time, which can severely degrade performance.
When client authentication is enabled in both of these ways, client authentication will be performed twice.
If you have a certificate signed by a trusted Certificate Authority (CA) such as Verisign, and the Application Server cacerts.jks file already contains a certificate verified by that CA, you do not need to complete this step. You only need to install your certificate in the Application Server certificate file when your certificate is self-signed.
From the directory where you want to create the client certificate, run keytool as outlined here. When you press Enter, keytool prompts you to enter the server name, organizational unit, organization, locality, state, and country code.
You must enter the server name in response to keytool’s first prompt, in which it asks for first and last names. For testing purposes, this can be localhost. The host specified in the keystore must match the host identified in the javee.server.host variable specified in your tut-install/javaeetutorial5/examples/bp-project/build.properties file. If this example is to verify mutual authentication and you receive a runtime error stating that the HTTPS host name is wrong, re-create the client certificate, being sure to use the same host name that you will use when running the example. For example, if your machine name is duke, then enter duke as the certificate CN or when prompted for first and last names. When accessing the application, enter a URL that points to the same location (for example, https://duke:8181/mutualauth/hello). This is necessary because during SSL handshake, the server verifies the client certificate by comparing the certificate name and the host name from which it originates.
To create a keystore named client_keystore.jks that contains a client certificate named client.cer, follow these steps:
Create a backup copy of the server truststore file. To do this,
Change to the directory containing the server’s keystore and truststore files, as-install\domains\domain1\config.
Copy cacerts.jks to cacerts.backup.jks.
Copy keystore.jks to keystore.backup.jks.
Do not put client certificates in the cacerts.jks file. Any certificate you add to the cacerts file effectively means it can be a trusted root for any and all certificate chains. After you have completed development, delete the development version of the cacerts file and replace it with the original copy.
Generate the client certificate. Enter the following command from the directory where you want to generate the client certificate:
java-home\bin\keytool -genkey -alias client-alias -keyalg RSA -keypass changeit -storepass changeit -keystore client_keystore.jks |
Export the generated client certificate into the file client.cer.
java-home\bin\keytool -export -alias client-alias -storepass changeit -file client.cer -keystore client_keystore.jks |
Add the certificate to the truststore file domain-dir/config/cacerts.jks. Run keytool from the directory where you created the keystore and client certificate. Use the following parameters:
java-home\bin\keytool -import -v -trustcacerts -alias client-alias -file client.cer -keystore domain-dir/config/cacerts.jks -keypass changeit -storepass changeit |
The keytool utility returns a message like this one:
Owner: CN=localhost, OU=Java EE, O=Sun, L=Santa Clara, ST=CA, C=US Issuer: CN=localhost, OU=Java EE, O=Sun, L=Santa Clara, ST=CA, C=US Serial number: 3e39e66a Valid from: Thu Jan 30 18:58:50 PST 2005 until: Wed Apr 3019:58:50 PDT 2005 Certificate fingerprints: MD5: 5A:B0:4C:88:4E:F8:EF:E9:E5:8B:53:BD:D0:AA:8E:5A SHA1:90:00:36:5B:E0:A7:A2:BD:67:DB:EA:37:B9:61:3E:26:B3:89:46:32 Trust this certificate? [no]: yes Certificate was added to keystore |
Restart the Application Server.
For more information about security in Java EE applications, see:
Java EE 5 Specification:
The Sun Java System Application Server 9.1 Developer’s Guide includes security information for application developers.
The Sun Java System Application Server 9.1 Administration Guide includes information on setting security settings for the Application Server.
The Sun Java System Application Server 9.1 Application Deployment Guide includes information on security settings in the deployment descriptors specific to the Application Server.
EJB 3.0 Specification (JSR-220):
Web Services for Java EE (JSR-109):
Java Platform, Standard Edition 6 security information:
http://java.sun.com/javase/6/docs/technotes/guides/security/
Java Servlet Specification, Version 2.5:
JSR 175: A Metadata Facility for the Java Programming Language:
JSR 181: Web Services Metadata for the Java Platform:
JSR 250: Common Annotations for the Java Platform:
The Java SE discussion of annotations:
http://java.sun.com/javase/6/docs/technotes/guides/language/annotations.html
The API specification for Java Authorization Contract for Containers:
Information on SSL specifications:
Chapter 24 of the CORBA/IIOP specification, Secure Interoperability:
Java Authentication and Authorization Service (JAAS) in Java Platform, Standard Edition:
http://java.sun.com/developer/technicalArticles/Security/jaasv2/index.html
Java Authentication and Authorization Service (JAAS) Reference Guide:
http://java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASRefGuide.html
Java Authentication and Authorization Service (JAAS): LoginModule Developer’s Guide:
http://java.sun.com/javase/6/docs/technotes/guides/security/jaas/JAASLMDevGuide.html
Java EE applications are made up of components that can be deployed into different containers. These components are used to build multitier enterprise applications. Security services are provided by the component container and can be implemented using declarative or programmatic techniques. Java EE security services provide a robust and easily configured security mechanism for authenticating users and authorizing access to application functions and associated data. Java EE security services are separate from the security mechanisms of the operating system.
The ways to implement Java EE security services are discussed in a general way in Securing Containers. This chapter provides more detail and a few examples that explore these security services as they relate to Java EE components. Java EE security services can be implemented in the following ways:
Metadata annotations (or simply, annotations) enable a declarative style of programming. Users can specify information about security within a class file using annotations. When the application is deployed, this information can either be used by or overridden by the application deployment descriptor.
Declarative security expresses an application’s security structure, including security roles, access control, and authentication requirements in a deployment descriptor, which is external to the application.
Any values explicitly specified in the deployment descriptor override any values specified in annotations.
Programmatic security is embedded in an application and is used to make security decisions. Programmatic security is useful when declarative security alone is not sufficient to express the security model of an application.
Some of the material in this chapter assumes that you have already read Chapter 28, Introduction to Security in the Java EE Platform.
This chapter includes the following topics:
Chapter 30, Securing Web Applications discusses security specific to web components such as servlets and JSP pages.
Enterprise beans are the Java EE components that implement Enterprise JavaBeans (EJB) technology. Enterprise beans run in the EJB container, a runtime environment within the Application Server, as shown in Figure 29–1.
Although transparent to the application developer, the EJB container provides system-level services such as transactions and security to its enterprise beans. These services enable you to quickly build and deploy enterprise beans, which form the core of transactional Java EE applications.
The following sections describe declarative and programmatic security mechanisms that can be used to protect enterprise bean resources. The protected resources include methods of enterprise beans that are called from application clients, web components, or other enterprise beans. This section assumes that you have read Chapter 20, Enterprise Beans and Chapter 21, Getting Started with Enterprise Beans before starting this section.
You can protect enterprise beans by doing the following:
Declaring Security Role Names Referenced from Enterprise Bean Code
Using Enterprise Bean Security Deployment Descriptor Elements
Two example applications demonstrate adding security to enterprise beans. These example applications are discussed in the following sections:
You should also read JSR-220: Enterprise JavaBeans 3.0 for more information on this topic. This document can be downloaded from http://jcp.org/en/jsr/detail?id=220. Chapter 16 of this specification, Security Management, discusses security management for enterprise beans.
In general, security management should be enforced by the container in a manner that is transparent to the enterprise beans’ business methods. The security API described in this section should be used only in the less frequent situations in which the enterprise bean business methods need to access the security context information.
The javax.ejb.EJBContext interface provides two methods that allow the bean provider to access security information about the enterprise bean’s caller.
java.security.Principal getCallerPrincipal();
The purpose of the getCallerPrincipal method is to allow the enterprise bean methods to obtain the current caller principal’s name. The methods might, for example, use the name as a key to information in a database.
boolean isCallerInRole(String roleName);
The purpose of the isCallerInRole(String roleName) method is to test whether the current caller has been assigned to a given security role. Security roles are defined by the bean provider or the application assembler, and are assigned to principals or principals groups that exist in the operational environment by the deployer.
The following code sample illustrates the use of the getCallerPrincipal() method:
@Stateless public class EmployeeServiceBean implements EmployeeService{ @Resource SessionContext ctx; @PersistenceContext EntityManager em; public void changePhoneNumber(...) { ... // obtain the caller principal. callerPrincipal = ctx.getCallerPrincipal(); // obtain the caller principal’s name. callerKey = callerPrincipal.getName(); // use callerKey as primary key to find EmployeeRecord EmployeeRecord myEmployeeRecord = em.findByPrimaryKey(EmployeeRecord.class, callerKey); // update phone number myEmployeeRecord.setPhoneNumber(...); ... } }
In the previous example, the enterprise bean obtains the principal name of the current caller and uses it as the primary key to locate an EmployeeRecord entity. This example assumes that application has been deployed such that the current caller principal contains the primary key used for the identification of employees (for example, employee number).
The following code sample illustrates the use of the isCallerInRole(String roleName) method:
@DeclareRoles("payroll") @Stateless public class PayrollBean implements Payroll { @Resource SessionContext ctx; public void updateEmployeeInfo(EmplInfo info) { oldInfo = ... read from database; // The salary field can be changed only by callers // who have the security role "payroll" if (info.salary != oldInfo.salary && !ctx.isCallerInRole("payroll")) { throw new SecurityException(...); } ... } ... }
An example application that uses the getCallerPrincipal and isCallerInRole methods is described in Example: Using the isCallerInRole and getCallerPrincipal Methods.
You can declare security role names used in enterprise bean code using either the @DeclareRoles annotation (preferred) or the security-role-ref elements of the deployment descriptor. Declaring security role names in this way enables you to link these security role names used in the code to the security roles defined for an assembled application. In the absence of this linking step, any security role name used in the code will be assumed to correspond to a security role of the same name in the assembled application.
A security role reference, including the name defined by the reference, is scoped to the component whose bean class contains the @DeclareRoles annotation or whose deployment descriptor element contains the security-role-ref deployment descriptor element.
You can also use the security-role-ref elements for those references that were declared in annotations and you want to have linked to a security-role whose name differs from the reference value. If a security role reference is not linked to a security role in this way, the container must map the reference name to the security role of the same name. See Linking Security Role References to Security Roles for a description of how security role references are linked to security roles.
For an example using each of these methods, read the following sections:
The @DeclareRoles annotation is specified on a bean class, where it serves to declare roles that can be tested by calling isCallerInRole from within the methods of the annotated class.
You declare the security roles referenced in the code using the @DeclareRoles annotation. When declaring the name of a role used as a parameter to the isCallerInRole(String roleName) method, the declared name must be the same as the parameter value. You can optionally provide a description of the named security roles in the description element of the @DeclareRoles annotation.
The following code snippet demonstrates the use of the @DeclareRoles annotation. In this example, the @DeclareRoles annotation indicates that the enterprise bean AardvarkPayroll makes the security check using isCallerInRole("payroll") to verify that the caller is authorized to change salary data. The security role reference is scoped to the session or entity bean whose declaration contains the @DeclareRoles annotation.
@DeclareRoles("payroll") @Stateless public class PayrollBean implements Payroll { @Resource SessionContext ctx; public void updateEmployeeInfo(EmplInfo info) { oldInfo = ... read from database; // The salary field can be changed only by callers // who have the security role "payroll" if (info.salary != oldInfo.salary && !ctx.isCallerInRole("payroll")) { throw new SecurityException(...); } ... } ... }
The syntax for declaring more than one role is as shown in the following example:
@DeclareRoles({"Administrator", "Manager", "Employee"})
Any values explicitly specified in the deployment descriptor override any values specified in annotations. If a value for a method has not been specified in the deployment descriptor, and a value has been specified for that method by means of the use of annotations, the value specified in annotations will apply. The granularity of overriding is on the per-method basis.
If the @DeclareRoles annotation is not used, you can use the security-role-ref elements of the deployment descriptor to declare the security roles referenced in the code, as follows:
Declare the name of the security role using the role-name element in the deployment descriptor. The name must be the security role name that is used as a parameter to the isCallerInRole(String roleName) method.
Optionally provide a description of the security role in the description element.
The following example illustrates how an enterprise bean’s references to security roles are declared in the deployment descriptor. In this example, the deployment descriptor indicates that the enterprise bean AardvarkPayroll makes the security check using isCallerInRole("payroll") in its business method. The security role reference is scoped to the session or entity bean whose declaration contains the security-role-ref element.
... <enterprise-beans> ... <session> <ejb-name>AardvarkPayroll</ejb-name> <ejb-class>com.aardvark.payroll.PayrollBean</ejb-class> ... <security-role-ref> <description> This security role should be assigned to the employees of the payroll department who are allowed to update employees’ salaries. </description> <role-name>payroll</role-name> </security-role-ref> ... </session> ... </enterprise-beans> ...
You can define a security view of the enterprise beans contained in the ejb-jar file and pass this information along to the deployer. When a security view is passed on to the deployer, the deployer uses this information to define method permissions for security roles. If you don’t define a security view, the deployer will have to determine what each business method does to determine which users are authorized to call each method.
A security view consists of a set of security roles, a semantic grouping of permissions that a given type of users of an application must have to successfully access the application. Security roles are meant to be logical roles, representing a type of user. You can define method permissions for each security role. A method permission is a permission to invoke a specified group of methods of the enterprise beans’ business interface, home interface, component interface, and/or web service endpoints. You can specify an authentication mechanism that will be used to verify the identity of a user.
It is important to keep in mind that security roles are used to define the logical security view of an application. They should not be confused with the user groups, users, principals, and other concepts that exist in the Application Server.
The following sections discuss setting up security roles, authentication mechanisms, and method permissions that define a security view:
Use the @DeclareRoles and @RolesAllowed annotations to define security roles using Java language annotations. The set of security roles used by the application is the total of the security roles defined by the security role names used in the @DeclareRoles and @RolesAllowed annotations.
You can augment the set of security roles defined for the application by annotations using the security-role deployment descriptor element to define security roles, where you use the role-name element to define the name of the security role.
The following example illustrates how to define security roles in a deployment descriptor:
... <assembly-descriptor> <security-role> <description> This role includes the employees of the enterprise who are allowed to access the employee self-service application. This role is allowed only to access his/her own information. </description> <role-name>employee</role-name> </security-role> <security-role> <description> This role includes the employees of the human resources department. The role is allowed to view and update all employee records. </description> <role-name>hr-department</role-name> </security-role> <security-role> <description> This role includes the employees of the payroll department. The role is allowed to view and update the payroll entry for any employee. </description> <role-name>payroll-department</role-name> </security-role> <security-role> <description> This role should be assigned to the personnel authorized to perform administrative functions for the employee self-service application. This role does not have direct access to sensitive employee and payroll information. </description> <role-name>admin</role-name> </security-role> ... </assembly-descriptor>
The security role references used in the components of the application are linked to the security roles defined for the application. In the absence of any explicit linking, a security role reference will be linked to a security role having the same name.
You can explicitly link all the security role references declared in the @DeclareRoles annotation or security-role-ref elements for a component to the security roles defined by the use of annotations (as discussed in Defining Security Roles) and/or in the security-role elements.
You use the role-link element to link each security role reference to a security role. The value of the role-link element must be the name of one of the security roles defined in a security-role element, or by the @DeclareRoles or @RolesAllowed annotations (as discussed in Defining Security Roles). You do not need to use the role-link element to link security role references to security roles when the role-name used in the code is the same as the name of the security-role to which you would be linking.
The following example illustrates how to link the security role reference name payroll to the security role named payroll-department:
... <enterprise-beans> ... <session> <ejb-name>AardvarkPayroll</ejb-name> <ejb-class>com.aardvark.payroll.PayrollBean</ejb-class> ... <security-role-ref> <description> This role should be assigned to the employees of the payroll department. Members of this role have access to anyone’s payroll record. The role has been linked to the payroll-department role. </description> <role-name>payroll</role-name> <role-link>payroll-department</role-link> </security-role-ref> ... </session> ... </enterprise-beans> ...
Authentications mechanisms are specified in the runtime deployment descriptor. When annotations, such as the @RolesAllowed annotation, are used to protect methods in the enterprise bean, you can configure the Interoperable Object Reference (IOR) to enable authentication for an enterprise application. This is accomplished by adding the <login-config>element to the runtime deployment descriptor, sun-ejb-jar.xml.
You can use the USERNAME-PASSWORD authentication method for an enterprise bean. You can use either the BASIC or CLIENT-CERT authentication methods for web service endpoints.
For more information on specifying an authentication mechanism, read Configuring IOR Security or Example: Securing an Enterprise Bean.
If you have defined security roles for the enterprise beans in the ejb-jar file, you can also specify the methods of the business interface, home interface, component interface, and/or web service endpoints that each security role is allowed to invoke.
You can use annotations and/or the deployment descriptor for this purpose. Refer to the following sections for more information on specifying method permissions:
The method permissions for the methods of a bean class can be specified on the class, the business methods of the class, or both. Method permissions can be specified on a method of the bean class to override the method permissions value specified on the entire bean class. The following annotations are used to specify method permissions:
@RolesAllowed("list-of-roles")
The value of the @RolesAllowed annotation is a list of security role names to be mapped to the security roles that are permitted to execute the specified method or methods. Specifying this annotation on the bean class means that it applies to all applicable business methods of the class.
The @PermitAll annotation specifies that all security roles are permitted to execute the specified method or methods. Specifying this annotation on the bean class means that it applies to all applicable business methods of the class.
The @DenyAll annotation specifies that no security roles are permitted to execute the specified method or methods.
The following example code illustrates the use of these annotations:
@RolesAllowed("admin") public class SomeClass { public void aMethod () {...} public void bMethod () {...} ... } @Stateless public class MyBean implements A extends SomeClass { @RolesAllowed("HR") public void aMethod () {...} public void cMethod () {...} ... }
In this example, assuming aMethod, bMethod, and cMethod are methods of business interface A, the method permissions values of methods aMethod and bMethod are @RolesAllowed("HR") and @RolesAllowed("admin") respectively. The method permissions for method cMethod have not been specified.
To clarify, the annotations are not inherited by the subclass per se, they apply to methods of the superclass which are inherited by the subclass. Also, annotations do not apply to CMP entity beans.
An example that uses annotations to specify method permissions is described in Example: Securing an Enterprise Bean.
Any values explicitly specified in the deployment descriptor override any values specified in annotations. If a value for a method has not been specified in the deployment descriptor, and a value has been specified for that method by means of the use of annotations, the value specified in annotations will apply. The granularity of overriding is on the per-method basis.
You define the method permissions in the deployment descriptor using the method-permission elements, as discussed below:
Each method-permission element includes a list of one or more security roles and a list of one or more methods. All the listed security roles are allowed to invoke all the listed methods. Each security role in the list is identified by the role-name element. Each method (or set of methods) is identified by the method element.
The method permissions relation is defined as the union of all the method permissions defined in the individual method-permission elements.
A security role or a method can appear in multiple method-permission elements.
Here is some other useful information about setting method permissions using deployment descriptors:
You can specify that all roles are permitted to execute one or more specified methods, which, in effect, indicates that the methods should not be checked for authorization prior to invocation by the container. To specify that all roles are permitted, use the unchecked element instead of a role name in a method permission.
If a method permission specifies both the unchecked element for a given method and one or more security roles, all roles are permitted for the specified methods.
The exclude-list element can be used to indicate the set of methods that should not be called. At deployment time, the deployer will know that no access is permitted to any method contained in this list.
If a given method is specified in both the exclude-list element and in a method permission, the deployer should exclude access to this method.
It is possible that some methods are not assigned to any security roles nor contained in the exclude-list element. In this case, the deployer should assign method permissions for all of the unspecified methods, either by assigning them to security roles, or by marking them as unchecked. If the deployer does not assign method permissions to the unspecified methods, those methods must be treated by the container as unchecked.
The method element uses the ejb-name, method-name, and method-params elements to denote one or more methods of an enterprise bean’s business interface, home interface, component interface, and/or web service endpoints.
There are three legal styles for composing the method element:
The first style is used for referring to all of the business interface, home interface, component interface, and web service endpoints methods of a specified bean.
<method> <ejb-name>EJB_NAME</ejb-name> <method-name>*</method-name> </method>
The second style is used for referring to a specified method of the business interface, home interface, component interface, or web service endpoints methods of the specified enterprise bean. If the enterprise bean contains multiple methods with the same overloaded name, the element of this style refers to all of the methods with the overloaded name.
<method> <ejb-name>EJB_NAME</ejb-name> <method-name>METHOD</method-name> </method>
The third style is used for referring to a specified method within a set of methods with an overloaded name. The method must be defined in the business interface, home interface, component interface, or web service endpoints methods of the specified enterprise bean. If there are multiple methods with the same overloaded name, however, this style refers to all of the overloaded methods. All of the parameters are the fully-qualified Java types, for example, java.lang.String.
<method> <ejb-name>EJB_NAME</ejb-name> <method-name>METHOD</method-name> <method-params> <method-param>PARAMETER_1</method-param> <method-param>PARAMETER_2</method-param> </method-params> </method>
The following example illustrates how security roles are assigned method permissions in the deployment descriptor:
... <method-permission> <role-name>employee</role-name> <method> <ejb-name>EmployeeService</ejb-name> <method-name>*</method-name> </method> </method-permission> <method-permission> <role-name>employee</role-name> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>findByPrimaryKey</method-name> </method> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>getEmployeeInfo</method-name> </method> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>updateEmployeeInfo</method-name> </method> </method-permission> <method-permission> <role-name>payroll-department</role-name> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>findByPrimaryKey</method-name> </method> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>getEmployeeInfo</method-name> </method> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>updateEmployeeInfo</method-name> </method> <method> <ejb-name>AardvarkPayroll</ejb-name> <method-name>updateSalary</method-name> </method> </method-permission> <method-permission> <role-name>admin</role-name> <method> <ejb-name>EmployeeServiceAdmin</ejb-name> <method-name>*</method-name> </method> </method-permission> ...
The Application Server assigns users to principals or groups, rather than to security roles. When you are developing a Java EE application, you don’t need to know what categories of users have been defined for the realm in which the application will be run. In the Java EE platform, the security architecture provides a mechanism for mapping the roles defined in the application to the users or groups defined in the runtime realm.
To map a role name permitted by the application or module to principals (users) and groups defined on the server, use the security-role-mapping element in the runtime deployment descriptor (sun-application.xml, sun-web.xml, or sun-ejb-jar.xml) file. The entry needs to declare a mapping between a security role used in the application and one or more groups or principals defined for the applicable realm of the Application Server. An example for the sun-application.xml file is shown below:
<sun-application> <security-role-mapping> <role-name>CEO</role-name> <principal-name>jschwartz</principal-name> </security-role-mapping> <security-role-mapping> <role-name>ADMIN</role-name> <group-name>directors</group-name> </security-role-mapping> </sun-application>
The role name can be mapped to either a specific principal (user), a group, or both. The principal or group names referenced must be valid principals or groups in the current default realm of the Application Server. The role-name in this example must exactly match the role-name in the security-role element of the corresponding web.xml file or the role name defined in the @DeclareRoles or @RolesAllowed annotations.
Sometimes the role names used in the application are the same as the group names defined on the Application Server. Under these circumstances, you can enable a default principal-to-role mapping on the Application Server using the Admin Console. To enable the default principal-to-role-mapping, follow these steps:
Start the Application Server, then the Admin Console.
Expand the Configuration node.
Select the Security node.
On the Security page, check the Enabled box beside Default Principal to Role Mapping.
For an enterprise application, you can specify the security role mapping at the application layer, in sun-application.xml, or at the module layer, in sun-ejb-jar.xml. When specified at the application layer, the role mapping applies to all contained modules and overrides same-named role mappings at the module layer. The assembler is responsible for reconciling the module-specific role mappings to yield one effective mapping for the application.
Both example applications demonstrate security role mapping. For more information, see Example: Securing an Enterprise Bean and Example: Using the isCallerInRole and getCallerPrincipal Methods.
You can specify whether a caller’s security identity should be used for the execution of specified methods of an enterprise bean, or whether a specific run-as identity should be used.
Figure 29–2 illustrates this concept.
In this illustration, an application client is making a call to an enterprise bean method in one EJB container. This enterprise bean method, in turn, makes a call to an enterprise bean method in another container. The security identity during the first call is the identity of the caller. The security identity during the second call can be any of the following options:
By default, the identity of the caller of the intermediate component is propagated to the target enterprise bean. This technique is used when the target container trusts the intermediate container.
A specific identity is propagated to the target enterprise bean. This technique is used when the target container expects access using a specific identity.
To propagate an identity to the target enterprise bean, configure a run-as identity for the bean as discussed in Configuring a Component’s Propagated Security Identity.
Establishing a run-as identity for an enterprise bean does not affect the identities of its callers, which are the identities tested for permission to access the methods of the enterprise bean. The run-as identity establishes the identity that the enterprise bean will use when it makes calls.
The run-as identity applies to the enterprise bean as a whole, including all the methods of the enterprise bean’s business interface, home interface, component interface, and web service endpoint interfaces, the message listener methods of a message-driven bean, the time-out callback method of an enterprise bean, and all internal methods of the bean that might be called in turn.
You can configure an enterprise bean’s run-as, or propagated, security identity using either of the following:
The following example illustrates the definition of a run-as identity using annotations:
@RunAs("admin") @Stateless public class EmployeeServiceBean implements EmployeeService { ... }
The role-name element of the run-as application deployment descriptor element (web.xml, ejb-jar.xml)
The following example illustrates the definition of a run-as identity using deployment descriptor elements:
... <enterprise-beans> ... <session> <ejb-name>EmployeeService</ejb-name> ... <security-identity> <run-as> <role-name>admin</role-name> </run-as> </security-identity> ... </session> ... </enterprise-beans> ...
Alternately, you can use the use-caller-identity element to indicate that you want to use the identity of the original caller, as shown in the code below:
<security-identity> <use-caller-identity /> </security-identity>
You must explicitly specify the run-as role name mapping to a given principal in sun-web.xml or sun-ejb-jar.xml if the given roles associate to more than one user principal.
More detail about the elements contained in deployment descriptors is available in the Sun Java System Application Server 9.1 Application Deployment Guide.
In either case, you will have to map the run-as role name to a given principal defined on the Application Server if the given roles associate to more than one user principal. Mapping roles to principals is described in Mapping Security Roles to Application Server Groups.
When an enterprise bean is designed so that either the original caller identity or a designated identity is used to call a target bean, the target bean will receive the propagated identity only; it will not receive any authentication data.
There is no way for the target container to authenticate the propagated security identity. However, because the security identity is used in authorization checks (for example, method permissions or with the isCallerInRole() method), it is vitally important that the security identity be authentic. Because there is no authentication data available to authenticate the propagated identity, the target must trust that the calling container has propagated an authenticated security identity.
By default, the Application Server is configured to trust identities that are propagated from different containers. Therefore, there are no special steps that you need to take to set up a trust relationship.
Annotations are used in code to relay information to the deployer about security and other aspects of the application. Specifying this information in annotations or in the deployment descriptor helps the deployer set up the appropriate security policy for the enterprise bean application.
Any values explicitly specified in the deployment descriptor override any values specified in annotations. If a value for a method has not been specified in the deployment descriptor, and a value has been specified for that method by means of the use of annotations, the value specified in annotations will apply. The granularity of overriding is on the per-method basis.
The following is a listing of annotations that address security, can be used in an enterprise bean, and are discussed in this tutorial:
The @DeclareRoles annotation declares each security role referenced in the code. Use of this annotation is discussed in Declaring Security Roles Using Annotations.
The @RolesAllowed, @PermitAll, and @DenyAll annotations are used to specify method permissions. Use of these annotations is discussed in Specifying Method Permissions Using Annotations.
The @RunAs metadata annotation is used to configure a component’s propagated security identity. Use of this annotation is discussed in Configuring a Component’s Propagated Security Identity.
Enterprise JavaBeans components use an EJB deployment descriptor that must be named META-INF/ejb-jar.xml and must be contained in the EJB JAR file. The role of the deployment descriptor is to relay information to the deployer about security and other aspects of the application. Specifying this information in annotations or in the deployment descriptor helps the deployer set up the appropriate security policy for the enterprise bean application. More detail about the elements contained in deployment descriptors is available in the Sun Java System Application Server 9.1 Application Deployment Guide.
Using annotations is the recommended method for adding security to enterprise bean applications.
Any values explicitly specified in the deployment descriptor override any values specified in annotations. If a value for a method has not been specified in the deployment descriptor, and a value has been specified for that method by means of the use of annotations, the value specified in annotations will apply. The granularity of overriding is on the per-method basis.
The following is a listing of deployment descriptor elements that address security, can be used in an enterprise bean, and are discussed in this tutorial:
The security-role-ref element declares each security role referenced in the code. Use of this element is discussed in Declaring Security Roles Using Deployment Descriptor Elements.
The security-role element defines broad categories of users, and is used to provide access to protected methods. Use of this element is discussed in Defining Security Roles.
The method-permission element is used to specify method permissions. Use of these elements is discussed in Specifying Method Permissions Using Deployment Descriptors.
The run-as element is used to configure a component’s propagated security identity. Use of this element is discussed in Configuring a Component’s Propagated Security Identity.
The schema for ejb-jar deployment descriptors can be found in section 18.5, Deployment Descriptor XML Schema, in the EJB 3.0 Specification (JSR-220) at http://jcp.org/en/jsr/detail?id=220.
The EJB interoperability protocol is based on Internet Inter-ORB Protocol (IIOP/GIOP 1.2) and the Common Secure Interoperability version 2 (CSIv2) CORBA Secure Interoperability specification.
Enterprise beans that are deployed in one vendor’s server product are often accessed from Java EE client components that are deployed in another vendor’s product. CSIv2, a CORBA/IIOP-based standard interoperability protocol, addresses this situation by providing authentication, protection of integrity and confidentiality, and principal propagation for invocations on enterprise beans, where the invocations take place over an enterprise’s intranet. CSIv2 configuration settings are specified in the Interoperable Object Reference (IOR) of the target enterprise bean. IOR configurations are defined in Chapter 24 of the CORBA/IIOP specification, Secure Interoperability. This chapter can be downloaded from http://www.omg.org/cgi-bin/doc?formal/02-06-60.
The EJB interoperability protocol is defined in Chapter 14, Support for Distribution and Interoperability, of the EJB specification, which can be downloaded from http://jcp.org/en/jsr/detail?id=220.
Based on application requirements, IORs are configured in vendor-specific XML files, such as sun-ejb-jar.xml, instead of in standard application deployment descriptor files, such as ejb-jar.xml.
For a Java EE application, IOR configurations are specified in Sun-specific xml files, for example, sun-ejb-jar_2_1-1.dtd. The ior-security-config element describes the security configuration information for the IOR. A description of some of the major subelements is provided below.
transport-config
This is the root element for security between the endpoints. It contains the following elements:
integrity: This element specifies whether the target supports integrity-protected messages for transport. The values are NONE, SUPPORTED, or REQUIRED.
confidentiality: This element specifies whether the target supports privacy-protected messages (SSL) for transport. The values are NONE, SUPPORTED, or REQUIRED.
establish-trust-in-target: This element specifies whether or not the target component is capable of authenticating to a client for transport. It is used for mutual authentication (to validate the server’s identity). The values are NONE, SUPPORTED, or REQUIRED.
establish-trust-in-client: This element specifies whether or not the target component is capable of authenticating a client for transport (target asks the client to authenticate itself). The values are NONE, SUPPORTED, or REQUIRED.
as-context
This is the element that describes the authentication mechanism (CSIv2 authentication service) that will be used to authenticate the client. If specified, it will be the username-password mechanism.
In the Duke’s Bank example, the as-context setting is used to require client authentication (with user name and password) when access to protected methods in the AccountControllerBean and CustomerControllerBean components is attempted.
The as-context element contains the following elements:
required: This element specifies whether the authentication method specified is required to be used for client authentication. Setting this field to true indicates that the authentication method specified is required. Setting this field to false indicates that the method authentication is not required. The element value is either true or false.
auth-method: This element specifies the authentication method. The only supported value is USERNAME_PASSWORD.
realm: This element specifies the realm in which the user is authenticated. Must be a valid realm that is registered in a server configuration.
sas-context
This element is related to the CSIv2 security attribute service. It describes the sas-context fields.
In the Duke’s Bank example, the sas-context setting is set to Supported for the AccountBean, CustomerBean, and TxBean components, indicating that these target components will accept propagated caller identities.
The sas-context element contains the caller-propagation subelement. This element indicates if the target will accept propagated caller identities. The values are NONE or SUPPORTED.
The following is an example that defines security for an IOR:
<sun-ejb-jar> <enterprise-beans> <unique-id>1</unique-id> <ejb> <ejb-name>HelloWorld</ejb-name> <jndi-name>HelloWorld</jndi-name> <ior-security-config> <transport-config> <integrity>NONE</integrity> <confidentiality>NONE</confidentiality> <establish-trust-in-target> NONE </establish-trust-in-target> <establish-trust-in-client> NONE </establish-trust-in-client> </transport-config> <as-context> <auth-method>USERNAME_PASSWORD</auth-method> <realm>default</realm> <required>true</required> </as-context> <sas-context> <caller-propagation>NONE</caller-propagation> </sas-context> </ior-security-config> <webservice-endpoint> <port-component-name>HelloIF</port-component-name> <endpoint-address-uri> service/HelloWorld </endpoint-address-uri> <login-config> <auth-method>BASIC</auth-method> </login-config> </webservice-endpoint> </ejb> </enterprise-beans> </sun-ejb-jar>
The deployer is responsible for ensuring that an assembled application is secure after it has been deployed in the target operational environment. If a security view (security annotations and/or a deployment descriptor) has been provided to the deployer, the security view is mapped to the mechanisms and policies used by the security domain in the target operational environment, which in this case is the Application Server. If no security view is provided, the deployer must set up the appropriate security policy for the enterprise bean application.
Deployment information is specific to a web or application server. Please read the Sun Java System Application Server 9.1 Application Deployment Guide for more information on deploying enterprise beans.
Web applications accept unauthenticated web clients and allow these clients to make calls to the EJB container. The EJB specification requires a security credential for accessing EJB methods. Typically, the credential will be that of a generic unauthenticated user. The way you specify this credential is implementation-specific.
In the Application Server, you must specify the name and password that an unauthenticated user will use to log in by modifying the Application Server using the Admin Console:
Start the Application Server, then the Admin Console.
Expand the Configuration node.
Select the Security node.
On the Security page, set the Default Principal and Default Principal Password values.
If the deployer has granted full access to a method, any user or group can invoke the method. Conversely, the deployer can deny access to a method.
To modify which role can be used in applications to grant authorization to anyone, specify a value for Anonymous Role. To set the Anonymous Role field, follow these steps:
Start the Application Server, then the Admin Console.
Expand the Configuration node.
Select the Security node.
On the Security page, specify the Anonymous Role value.
The following example applications demonstrate adding security to enterprise beans applications:
Example: Securing an Enterprise Bean demonstrates adding basic login authentication to an enterprise bean application.
Example: Using the isCallerInRole and getCallerPrincipal Methods demonstrates the use of the getCallerPrincipal() and isCallerInRole(String role) methods.
Discussion: Securing the Duke’s Bank Example provides a brief discussion of how the Duke’s Bank example provides security in that application.
This section discusses how to configure an enterprise bean for username-password authentication. When a bean that is constrained in this way is requested, the server requests a user name and password from the client and verifies that the user name and password are valid by comparing them against a database of authorized users on the Application Server.
If the topic of authentication is new to you, please refer to the section titled Specifying an Authentication Mechanism.
For this tutorial, you will add the security elements to an enterprise bean; add security elements to the deployment descriptors; build, package, and deploy the application; and then build and run the client application.
The completed version of this example can be found at tut-install/javaeetutorial5/examples/ejb/cart-secure/. This example was developed by starting with the unsecured enterprise bean application, cart, which is found in the directory tut-install/javaeetutorial5/examples/ejb/cart/ and is discussed in The cart Example. You build on this example by adding the necessary elements to secure the application using username-password authentication.
In general, the following steps are necessary to add username-password authentication to an enterprise bean. In the example application included with this tutorial, many of these steps have been completed for you and are listed here simply to show what needs to be done should you wish to create a similar application.
Create an application like the one in The cart Example. The example in this tutorial starts with this example and demonstrates adding basic authentication of the client to this application. The example application discussed in this section can be found at tut-install/javaeetutorial5/examples/ejb/cart-secure/.
If you have not already done so, complete the steps in Building the Examples to configure your system properly for running the tutorial applications.
If you have not already done so, add a user to the file realm and specify user for the group of this new user. Write down the user name and password so that you can use them for testing this application in a later step. Refer to the section Managing Users and Groups on the Application Server for instructions on completing this step.
Modify the source code for the enterprise bean, CartBean.java, to specify which roles are authorized to access which protected methods. This step is discussed in Annotating the Bean.
Modify the runtime deployment descriptor, sun-ejb-jar.xml, to map the role used in this application (CartUser) to a group defined on the Application Server (user) and to add security elements that specify that username-password authentication is to be performed. This step is discussed in Setting Runtime Properties.
Build, package, and deploy the enterprise bean, then build and run the client application by following the steps in Building, Deploying, and Running the Secure Cart Example Using NetBeans IDE or Building, Deploying, and Running the Secure Cart Example Using Ant.
The source code for the original cart application was modified as shown in the following code snippet (modifications in bold, method details removed to save space). The resulting file can be found in the following location:
tut-install/javaeetutorial5/examples/ejb/cart-secure/cart-secure-ejb/src/java/cart/secure/ ejb/CartBean.java
The code snippet is as follows:
package com.sun.tutorial.javaee.ejb; import java.util.ArrayList; import java.util.List; import javax.ejb.Remove; import javax.ejb.Stateful; import javax.annotation.security.RolesAllowed; @Stateful() public class CartBean implements Cart { String customerName; String customerId; List<String> contents; public void initialize(String person) throws BookException { ... } public void initialize(String person, String id) throws BookException { ... } @RolesAllowed("CartUser") public void addBook(String title) { contents.add(title); } @RolesAllowed("CartUser") public void removeBook(String title) throws BookException { ... } } @RolesAllowed("CartUser") public List<String> getContents() { return contents; } @Remove() public void remove() { contents = null; } }
The @RolesAllowed annotation is specified on methods for which you want to restrict access. In this example, only users in the role of CartUser will be allowed to add and remove books from the cart, and to list the contents of the cart. An @RolesAllowed annotation implicitly declares a role that will be referenced in the application; therefore, no @DeclareRoles annotation is required.
The role of CartUser has been defined for this application, but there is no group of CartUser defined for the Application Server. To map the role that is defined for the application (CartUser) to a group that is defined on the Application Server (user), add a <security-role-mapping> element to the runtime deployment descriptor, sun-ejb-jar.xml, as shown below. In the original example, there was no need for this deployment descriptor, so it has been added for this example.
To enable username-password authentication for the application, add security elements to the runtime deployment descriptor, sun-ejb-jar.xml. The security element that needs to be added to the deployment descriptor is the <ior-security-config> element. The deployment descriptor is located in tut-install/javaeetutorial5/examples/ejb/cart-secure/cart-secure-ejb/src/conf/sun-ejb-jar.xml.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd"> <sun-ejb-jar> <security-role-mapping> <role-name>CartUser</role-name> <group-name>user</group-name> </security-role-mapping> <enterprise-beans> <unique-id>0</unique-id> <ejb> <ejb-name>CartBean</ejb-name> <jndi-name>jacc_mr_CartBean</jndi-name> <pass-by-reference>false</pass-by-reference> <ior-security-config> <transport-config> <integrity>supported</integrity> <confidentiality>supported</confidentiality> <establish-trust-in-target>supported</establish-trust-in-target> <establish-trust-in-client>supported</establish-trust-in-client> </transport-config> <as-context> <auth-method>username_password</auth-method> <realm>default</realm> <required>true</required> </as-context> <sas-context> <caller-propagation>supported</caller-propagation> </sas-context> </ior-security-config> <is-read-only-bean>false</is-read-only-bean> <refresh-period-in-seconds>-1</refresh-period-in-seconds> <gen-classes/> </ejb> </enterprise-beans> </sun-ejb-jar>
For more information on this topic, read Specifying an Authentication Mechanism and Configuring IOR Security.
Follow these instructions to build, deploy, and run the cart-secure example in your Application Server instance using NetBeans IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/ejb/.
Select the cart-secure folder.
Select the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the cart-secure project and select Clean and Build.
In the Projects tab, right-click the cart-secure project and select Undeploy and Deploy.
This step builds and packages the application into cart-secure.ear, located in tut-install/javaeetutorial5/examples/ejb/cart-secure/dist/, and deploys this ear file to your Application Server instance.
To run secure cart’s application client, select Run->Run Main Project. You will be prompted for your username and password.
Enter the username and password of a user that has been entered into the database of users for the file realm and has been assigned to the group of user.
If the username and password you enter are authorized, you will see the output of the application client in the Output pane:
... Retrieving book title from cart: Infinite Jest Retrieving book title from cart: Bel Canto Retrieving book title from cart: Kafka on the Shore Removing "Gravity’s Rainbow" from cart. Caught a BookException: "Gravity’s Rainbow" not in cart. Java Result: 1 run-cart-secure-app-client: |
To build, deploy, and run the secure EJB example using the Ant tool, follow these steps:
If you have not already done so, specify properties specific to your installation in the tut-install/javaeetutorial5/examples/bp-project/build.properties file and the tut-install/javaeetutorial5/examples/common/admin-password.txt file. See Building the Examples for information on which properties need to be set in which files.
If you have not already done so, add a user to the file realm and specify user for the group of this new user. Refer to the section Managing Users and Groups on the Application Server for instructions on completing this step.
From a terminal window or command prompt, go to the tut-install/javaeetutorial5/examples/ejb/cart-secure/ directory.
Build, package, and deploy the enterprise application, and build and run the client, by entering the following at the terminal window or command prompt in the ejb/cart-secure/ directory:
ant all |
This step assumes that you have the executable for ant in your path; if not, you will need to provide the fully qualified path to the ant executable. This command runs the ant target named all in the build.xml file.
A Login for User dialog displays. Enter a user name and password that correspond to a user set up on the Application Server with a group of user. Click OK.
If the user name and password are authenticated, the client displays the following output:
run: [echo] Running appclient for Cart. appclient-command-common: [exec] Infinite Jest [exec] Bel Canto [exec] Kafka on the Shore [exec] Caught a BookException: "Gravity’s Rainbow" not in cart. |
If the username and password are not authenticated, the client displays the following error:
run: [echo] Running appclient for Cart. appclient-command-common: [exec] Caught an unexpected exception! [exec] javax.ejb.EJBException: nested exception is: java.rmi.AccessException: CORBA NO_PERMISSION 9998 Maybe; nested exception is: [exec] org.omg.CORBA.NO_PERMISSION: ----------BEGIN server-side stack trace---------- [exec] org.omg.CORBA.NO_PERMISSION: vmcid: 0x2000 minor code: 1806 |
If you see this response, verify the user name and password of the user that you entered in the login dialog, make sure that user is assigned to the group user, and rerun the client application.
This example demonstrates how to use the getCallerPrincipal() and isCallerInRole(String role) methods with an enterprise bean. This example starts with a very simple EJB application, converter, and modifies the methods of the ConverterBean so that currency conversion will only occur when the requester is in the role of BeanUser.
For this tutorial, you will add the security elements to an enterprise bean; add the security elements to the deployment descriptor; build, package, and deploy the application; and then build and run the client application. The completed version of this example can be found at tut-install/javaeetutorial5/examples/ejb/converter-secure. This example was developed by starting with the unsecured enterprise bean application, converter, which is discussed in Chapter 21, Getting Started with Enterprise Beans and is found in the directory tut-install/javaeetutorial5/examples/ejb/converter/. This section builds on this example by adding the necessary elements to secure the application using the getCallerPrincipal() and isCallerInRole(String role) methods, which are discussed in more detail in Accessing an Enterprise Bean Caller’s Security Context.
In general, the following steps are necessary when using the getCallerPrincipal() and isCallerInRole(String role) methods with an enterprise bean. In the example application included with this tutorial, many of these steps have been completed for you and are listed here simply to show what needs to be done should you wish to create a similar application.
Create a simple enterprise bean application, such as the converter example. See Chapter 21, Getting Started with Enterprise Beans for more information on creating and understanding this example. This section of the tutorial starts with this unsecured application and demonstrates how to access an enterprise bean caller’s security context. The completed example application discussed in this section can be found at tut-install/javaeetutorial5/examples/ejb/converter-secure/.
If you have not already done so, follow the steps in Building the Examples to set properties specific to your installation.
If you have not already done so, set up a user on the Application Server in the file realm. Make sure that the user is included in the group named user. For information on adding a user to the file realm, read Managing Users and Groups on the Application Server.
Modify ConverterBean to add the getCallerPrincipal() and isCallerInRole(String role) methods. For this example, callers that are in the role of BeanUser will be able to calculate the currency conversion. Callers not in the role of BeanUser will see a value of zero for the conversion amount. Modifying the ConverterBean code is discussed in Modifying ConverterBean.
Modify the sun-ejb-jar.xml file to specify a secure connection, username-password login, and security role mapping. Modifying the sun-ejb-jar.xml file is discussed in Modifying Runtime Properties for the Secure Converter Example.
Build, package, deploy, and run the application. These steps are discussed in Building, Deploying, and Running the Secure Converter Example Using NetBeans IDE and Building, Deploying, and Running the Secure Converter Example Using Ant.
If necessary, refer to the tips in Troubleshooting the Secure Converter Application for tips on errors you might encounter and some possible solutions.
The source code for the original converter application was modified as shown in the following code snippet (modifications in bold) to add the if..else clause that tests if the caller is in the role of BeanUser. If the user is in the correct role, the currency conversion is computed and displayed. If the user is not in the correct role, the computation is not performed, and the application displays the result as 0. The code example can be found in the following file:
tut-install/javaeetutorial5/examples/ejb/converter-secure/converter-secure-ejb/src/java/ converter/secure/ejb/ConverterBean.java
The code snippet is as follows:
package converter.secure.ejb; import java.math.BigDecimal; import javax.ejb.*; import java.security.Principal; import javax.annotation.Resource; import javax.ejb.SessionContext; import javax.annotation.security.DeclareRoles; import javax.annotation.security.RolesAllowed; @Stateless() @DeclareRoles("BeanUser") public class ConverterBean implements converter.secure.ejb.Converter { @Resource SessionContext ctx; private BigDecimal yenRate = new BigDecimal("115.3100"); private BigDecimal euroRate = new BigDecimal("0.0071"); @RolesAllowed("BeanUser") public BigDecimal dollarToYen(BigDecimal dollars) { BigDecimal result = new BigDecimal("0.0"); Principal callerPrincipal = ctx.getCallerPrincipal(); if (ctx.isCallerInRole("BeanUser")) { result = dollars.multiply(yenRate); return result.setScale(2, BigDecimal.ROUND_UP); }else{ return result.setScale(2, BigDecimal.ROUND_UP); } } @RolesAllowed("BeanUser") public BigDecimal yenToEuro(BigDecimal yen) { BigDecimal result = new BigDecimal("0.0"); Principal callerPrincipal = ctx.getCallerPrincipal(); if (ctx.isCallerInRole("BeanUser")) { result = yen.multiply(euroRate); return result.setScale(2, BigDecimal.ROUND_UP); }else{ return result.setScale(2, BigDecimal.ROUND_UP); } } }
Secure connections, username-password login, and the mapping of application roles to Application Server groups and principals are specified in the runtime deployment descriptor file sun-ejb-jar.xml. The original converter application that did not include any security mechanisms did not have a need for this file: it has been added specifically for this application.
To map the role of BeanUser that is defined for this application to the group with the name of user in the file realm of the Application Server, specify the security-role-mapping element as shown below. Make sure that the role-name and group-name elements are specified exactly as they are used (the mapping is case-sensitive).
To specify username-password login and a secure connection, use the ior-security-config element. The IOR security elements are described in more detail in Configuring IOR Security.
The following sun-ejb-jar.xml file demonstrates how to specify a secure connection, username-password login, and security role mapping. The completed version of this file can be found in tut-install/javaeetutorial5/examples/ejb/converter-secure/converter-secure-ejb/src/conf/sun-ejb-jar.xml.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 EJB 3.0//EN" "http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_0-0.dtd"> <sun-ejb-jar> <security-role-mapping> <role-name>BeanUser</role-name> <group-name>user</group-name> </security-role-mapping> <enterprise-beans> <unique-id>0</unique-id> <ejb> <ejb-name>ConverterBean</ejb-name> <jndi-name>ConverterBean</jndi-name> <pass-by-reference>false</pass-by-reference> <ior-security-config> <transport-config> <integrity>supported</integrity> <confidentiality>supported</confidentiality> <establish-trust-in-target> supported </establish-trust-in-target> <establish-trust-in-client> supported </establish-trust-in-client> </transport-config> <as-context> <auth-method>username_password</auth-method> <realm>file</realm> <required>true</required> </as-context> <sas-context> <caller-propagation> supported </caller-propagation> </sas-context> </ior-security-config> <is-read-only-bean>false</is-read-only-bean> <refresh-period-in-seconds> -1 </refresh-period-in-seconds> <gen-classes/> </ejb> </enterprise-beans> </sun-ejb-jar
Follow these instructions to build, package, and deploy the converter-secure example to your Application Server instance using NetBeans IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/ejb/.
Select the converter-secure folder.
Select the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the converter-secure project and select Clean and Build.
In the Projects tab, right-click the converter-secure project and select Undeploy and Deploy.
This step builds and packages the application into converter-secure.ear, located in tut-install/javaeetutorial5/examples/ejb/converter-secure/dist/, and deploys this ear file to your Application Server instance.
To run the secure converter’s application client, select Run->Run Main Project. You will be prompted for your username and password.
Enter the username and password of a user that has been entered into the database of users for the file realm and has been assigned to the group of user.
If the username and password you enter are authorized, you will see the output of the application client in the Output pane:
[exec] $100.00 is 11531.00 Yen. [exec] 11531.00 Yen is 81.88 Euro. |
To build the secure converter enterprise beans and client, package and deploy the enterprise application, and run the client application, follow these steps:
Set up your system for running the tutorial examples if you haven’t done so already by following the instructions in Building the Examples.
From a terminal window or command prompt, go to the tut-install/javaeetutorial5/examples/ejb/converter-secure/ directory.
Build, package, deploy, and run the enterprise application and application client by entering the following at the terminal window or command prompt in the ejb/converter-secure/ directory:
ant all |
This step assumes that you have the executable for ant in your path; if not, you will need to provide the fully qualified path to the ant executable. This command runs the ant target named all in the build.xml file.
The running application will look like this:
appclient-command-common: |
At this point, a system login dialog will display. Enter the user name and password that correspond to a user in the group user on the Application Server. If the user name and password are authenticated, the following text displays in the terminal window or command prompt:
appclient-command-common: [exec] $100.00 is 11531.00 Yen. [exec] 11531.00 Yen is 81.88 Euro. |
Problem: The application displays zero values after authentication, as shown here:
appclient-command-common: [exec] $100.00 is 0.00 Yen. [exec] 0.00 Yen is 0.00 Euro. |
Solution: Verify that the user name and password that you entered for authentication match a user name and password in the Application Server, and that this user is assigned to the group named user. User names and passwords are case-sensitive. Read Adding Users to the Application Server for more information on adding users to the file realm of the Application Server.
The Duke’s Bank application is an online banking application. Duke’s Bank has two clients: an application client used by administrators to manage customers and accounts, and a web client used by customers to access account histories and perform transactions. The clients access the customer, account, and transaction information maintained in a database through enterprise beans. The Duke’s Bank application demonstrates the way that many of the component technologies presented in this tutorial (enterprise beans, application clients, and web components) are applied to provide a simple but functional application.
To secure the Duke’s Bank example, the following security mechanisms are used:
Defining security roles
Specifying form-based user authentication for the web client in a security constraint
Adding authorized users and groups to the appropriate Application Server realm
Specifying method permissions for enterprise beans
Configuring Interoperable Object References (IOR)
Read Chapter 37, The Duke’s Bank Application for more information on securing the Duke’s Bank example.
The Java EE authentication requirements for application clients are the same as for other Java EE components, and the same authentication techniques can be used as for other Java EE application components.
No authentication is necessary when accessing unprotected web resources. When accessing protected web resources, the usual varieties of authentication can be used, namely HTTP basic authentication, SSL client authentication, or HTTP login form authentication. These authentication methods are discussed in Specifying an Authentication Mechanism.
Authentication is required when accessing protected enterprise beans. The authentication mechanisms for enterprise beans are discussed in Securing Enterprise Beans. Lazy authentication can be used.
An application client makes use of an authentication service provided by the application client container for authenticating its users. The container’s service can be integrated with the native platform’s authentication system, so that a single sign-on capability is employed. The container can authenticate the user when the application is started, or it can use lazy authentication, authenticating the user when a protected resource is accessed.
An application client can provide a class to gather authentication data. If so, the javax.security.auth.callback.CallbackHandler interface must be implemented, and the class name must be specified in its deployment descriptor. The application’s callback handler must fully support Callback objects specified in the javax.security.auth.callback package. Gathering authentication data in this way is discussed in the next section, Using Login Modules.
An application client can use the Java Authentication and Authorization Service (JAAS) to create login modules for authentication. A JAAS-based application implements the javax.security.auth.callback.CallbackHandler interface so that it can interact with users to enter specific authentication data, such as user names or passwords, or to display error and warning messages.
Applications implement the CallbackHandler interface and pass it to the login context, which forwards it directly to the underlying login modules. A login module uses the callback handler both to gather input (such as a password or smart card PIN) from users and to supply information (such as status information) to users. Because the application specifies the callback handler, an underlying login module can remain independent of the various ways that applications interact with users.
For example, the implementation of a callback handler for a GUI application might display a window to solicit user input. Or the implementation of a callback handler for a command-line tool might simply prompt the user for input directly from the command line.
The login module passes an array of appropriate callbacks to the callback handler’s handle method (for example, a NameCallback for the user name and a PasswordCallback for the password); the callback handler performs the requested user interaction and sets appropriate values in the callbacks. For example, to process a NameCallback, the CallbackHandler might prompt for a name, retrieve the value from the user, and call the setName method of the NameCallback to store the name.
For more information on using JAAS for login modules for authentication, refer to the following sources:
Java Authentication and Authorization Service (JAAS) in Java Platform, Standard Edition
Java Authentication and Authorization Service (JAAS) Reference Guide
Java Authentication and Authorization Service (JAAS): LoginModule Developer’s Guide
Links to this information are provided in Further Information about Security.
Programmatic login enables the client code to supply user credentials. If you are using an EJB client, you can use the com.sun.appserv.security.ProgrammaticLogin class with their convenient login and logout methods.
Because programmatic login is specific to a server, information on programmatic login is not included in this document, but is included in the Sun Java System Application Server 9.1 Developer’s Guide.
In EIS applications, components request a connection to an EIS resource. As part of this connection, the EIS can require a sign-on for the requester to access the resource. The application component provider has two choices for the design of the EIS sign-on:
In the container-managed sign-on approach, the application component lets the container take the responsibility of configuring and managing the EIS sign-on. The container determines the user name and password for establishing a connection to an EIS instance. For more information, read Container-Managed Sign-On.
In the component-managed sign-on approach, the application component code manages EIS sign-on by including code that performs the sign-on process to an EIS. For more information, read Component-Managed Sign-On.
You can also configure security for resource adapters. Read Configuring Resource Adapter Security for more information.
In container-managed sign-on, an application component does not have to pass any sign-on security information to the getConnection() method. The security information is supplied by the container, as shown in the following example.
// Business method in an application component Context initctx = new InitialContext(); // Perform JNDI lookup to obtain a connection factory javax.resource.cci.ConnectionFactory cxf = (javax.resource.cci.ConnectionFactory)initctx.lookup( "java:comp/env/eis/MainframeCxFactory"); // Invoke factory to obtain a connection. The security // information is not passed in the getConnection method javax.resource.cci.Connection cx = cxf.getConnection(); ...
In component-managed sign-on, an application component is responsible for passing the needed sign-on security information to the resource to the getConnection method. For example, security information might be a user name and password, as shown here:
// Method in an application component Context initctx = new InitialContext(); // Perform JNDI lookup to obtain a connection factory javax.resource.cci.ConnectionFactory cxf = (javax.resource.cci.ConnectionFactory)initctx.lookup( "java:comp/env/eis/MainframeCxFactory"); // Get a new ConnectionSpec com.myeis.ConnectionSpecImpl properties = //.. // Invoke factory to obtain a connection properties.setUserName("..."); properties.setPassword("..."); javax.resource.cci.Connection cx = cxf.getConnection(properties); ...
A resource adapter is a system-level software component that typically implements network connectivity to an external resource manager. A resource adapter can extend the functionality of the Java EE platform either by implementing one of the Java EE standard service APIs (such as a JDBC driver), or by defining and implementing a resource adapter for a connector to an external application system. Resource adapters can also provide services that are entirely local, perhaps interacting with native resources. Resource adapters interface with the Java EE platform through the Java EE service provider interfaces (Java EE SPI). A resource adapter that uses the Java EE SPIs to attach to the Java EE platform will be able to work with all Java EE products.
To configure the security settings for a resource adapter, you need to edit the ra.xml file. Here is an example of the part of an ra.xml file that configures the following security properties for a resource adapter:
<authentication-mechanism> <authentication-mechanism-type>BasicPassword</authentication-mechanism-type> <credential-interface> javax.resource.spi.security.PasswordCredential </credential-interface> </authentication-mechanism> <reauthentication-support>false</reauthentication-support>
You can find out more about the options for configuring resource adapter security by reviewing as-install/lib/dtds/connector_1_0.dtd. You can configure the following elements in the resource adapter deployment descriptor file:
Authentication mechanisms
Use the authentication-mechanism element to specify an authentication mechanism supported by the resource adapter. This support is for the resource adapter and not for the underlying EIS instance.
There are two supported mechanism types:
BasicPassword: This mechanism supports the interface javax.resource.spi.security.PasswordCredential.
Kerbv5: This mechanism supports the interface javax.resource.spi.security.GenericCredential. The Application Server does not currently support this mechanism type.
Reauthentication support
Use the reauthentication-support element to specify whether the resource adapter implementation supports re-authentication of existing Managed-Connection instances. Options are true or false.
Security permissions
Use the security-permission element to specify a security permission that is required by the resource adapter code. Support for security permissions is optional and is not supported in the current release of the Application Server. You can, however, manually update the server.policy file to add the relevant permissions for the resource adapter, as described in the Developing and Deploying Applications section of the Sun Java System Application Server 9.1 Developer’s Guide.
The security permissions listed in the deployment descriptor are ones that are different from those required by the default permission set as specified in the connector specification.
Refer to the following URL for more information on Sun’s implementation of the security permission specification: http://java.sun.com/javase/6/docs/technotes/guides/security/PolicyFiles.html#FileSyntax.
In addition to specifying resource adapter security in the ra.xml file, you can create a security map for a connector connection pool to map an application principal or a user group to a back end EIS principal. The security map is usually used in situations where one or more EIS back end principals are used to execute operations (on the EIS) initiated by various principals or user groups in the application. You can find out more about security maps in the Configuring Security chapter section of the Sun Java System Application Server 9.1 Administration Guide.
When using the Application Server, you can use security maps to map the caller identity of the application (principal or user group) to a suitable EIS principal in container-managed transaction-based scenarios. When an application principal initiates a request to an EIS, the Application Server first checks for an exact principal using the security map defined for the connector connection pool to determine the mapped back end EIS principal. If there is no exact match, then the Application Server uses the wild card character specification, if any, to determine the mapped back-end EIS principal. Security maps are used when an application user needs to execute EIS operations that require to be executed as a specific identity in the EIS.
To work with security maps, use the Admin Console. From the Admin Console, follow these steps to get to the security maps page:
Expand the Resources node.
Expand the Connectors node.
Select the Connector Connection Pools node.
Select a Connector Connection Pool by selecting its name from the list of current pools, or create a new connector connection pool by selecting New from the list of current pools.
Select the Security Maps page.
Web applications contain resources that can be accessed by many users. These resources often traverse unprotected, open networks, such as the Internet. In such an environment, a substantial number of web applications will require some type of security.
The ways to implement security for Java EE applications are discussed in a general way in Securing Containers. This chapter provides more detail and a few examples that explore these security services as they relate to web components.
Java EE security services can be implemented for web applications in the following ways:
Metadata annotations (or simply, annotations) are used to specify information about security within a class file. When the application is deployed, this information can either be used by or overridden by the application deployment descriptor.
Declarative security expresses an application’s security structure, including security roles, access control, and authentication requirements in a deployment descriptor, which is external to the application.
Any values explicitly specified in the deployment descriptor override any values specified in annotations.
Programmatic security is embedded in an application and is used to make security decisions. Programmatic security is useful when declarative security alone is not sufficient to express the security model of an application.
Some of the material in this chapter assumes that you have already read Chapter 28, Introduction to Security in the Java EE Platform. This chapter also assumes that you are familiar with the web technologies discussed in Chapter 3, Getting Started with Web Applications, Chapter 5, JavaServer Pages Technology, and Chapter 10, JavaServer Faces Technology.
In the Java EE platform, web components provide the dynamic extension capabilities for a web server. Web components are either Java servlets, JSP pages, JSF pages, or web service endpoints. The interaction between a web client and a web application is illustrated in Figure 30–1.
Web components are supported by the services of a runtime platform called a web container. A web container provides services such as request dispatching, security, concurrency, and life-cycle management.
Certain aspects of web application security can be configured when the application is installed, or deployed, to the web container. Annotations and/or deployment descriptors are used to relay information to the deployer about security and other aspects of the application. Specifying this information in annotations or in the deployment descriptor helps the deployer set up the appropriate security policy for the web application. Any values explicitly specified in the deployment descriptor override any values specified in annotations. This chapter provides more information on configuring security for web applications.
For secure transport, most web applications use the HTTPS protocol. For more information on using the HTTPS protocol, read Establishing a Secure Connection Using SSL.
If you read Working with Realms, Users, Groups, and Roles, you will remember the following definitions:
In applications, roles are defined using annotations or in application deployment descriptors such as web.xml, ejb-jar.xml, and application.xml.
A role is an abstract name for the permission to access a particular set of resources in an application. For more information, read What Is a Role?.
For more information on defining roles, see Declaring Security Roles.
On the Application Server, the following options are configured using the Admin Console:
A realm is a complete database of users and groups that identify valid users of a web application (or a set of web applications) and are controlled by the same authentication policy. For more information, read What Is a Realm?.
A user is an individual (or application program) identity that has been defined in the Application Server. On the Application Server, a user generally has a user name, a password, and, optionally, a list of groups to which this user has been assigned. For more information, read What Is a User?.
A group is a set of authenticated users, classified by common traits, defined in the Application Server. For more information, read What Is a Group?.
A principal is an entity that can be authenticated by an authentication protocol in a security service that is deployed in an enterprise.
For more information on configuring users on the Application Server, read Managing Users and Groups on the Application Server.
During deployment, the deployer takes the information provided in the application deployment descriptor and maps the roles specified for the application to users and groups defined on the server using the Application Server deployment descriptors sun-web.xml, sun-ejb-jar.xml, or sun-application.xml.
For more information, read Mapping Security Roles to Application Server Groups.
You can declare security role names used in web applications using either the @DeclareRoles annotation (preferred) or the security-role-ref elements of the deployment descriptor. Declaring security role names in this way enables you to link the security role names used in the code to the security roles defined for an assembled application. In the absence of this linking step, any security role name used in the code will be assumed to correspond to a security role of the same name in the assembled application.
A security role reference, including the name defined by the reference, is scoped to the component whose class contains the @DeclareRoles annotation or whose deployment descriptor element contains the security-role-ref deployment descriptor element.
You can also use the security-role-ref elements for those references that were declared in annotations and you want to have linked to a security-role whose name differs from the reference value. If a security role reference is not linked to a security role in this way, the container must map the reference name to the security role of the same name. See Declaring and Linking Role References for a description of how security role references are linked to security roles.
For an example using each of these methods, read the following sections:
Annotations are the best way to define security roles on a class or a method. The @DeclareRoles annotation is used to define the security roles that comprise the security model of the application. This annotation is specified on a class, and it typically would be used to define roles that could be tested (for example, by calling isUserInRole) from within the methods of the annotated class.
Following is an example of how this annotation would be used. In this example, employee is the only security role specified, but the value of this parameter can include a list of security roles specified by the application.
@DeclareRoles("employee") public class CalculatorServlet { //... }
Specifying @DeclareRoles("employee") is equivalent to defining the following in the web.xml:
<security-role> <role-name>employee</role-name> </security-role>
This annotation is not used to link application roles to other roles. When such linking is necessary, it is accomplished by defining an appropriate security-role-ref in the associated deployment descriptor, as described in Declaring and Linking Role References.
When a call is made to isUserInRole from the annotated class, the caller identity associated with the invocation of the class is tested for membership in the role with the same name as the argument to isUserInRole. If a security-role-ref has been defined for the argument role-name, the caller is tested for membership in the role mapped to the role-name.
The following snippet of a deployment descriptor is taken from the simple sample application. This snippet includes all of the elements needed to specify security roles using deployment descriptors:
<servlet> ... <security-role-ref> <role-name>MGR</role-name> <!-- role name used in code --> <role-link>employee</role-link> </security-role-ref> </servlet> <security-constraint> <web-resource-collection> <web-resource-name>Protected Area</web-resource-name> <url-pattern>/jsp/security/protected/*</url-pattern> <http-method>PUT</http-method> <http-method>DELETE</http-method> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>role1</role-name> <role-name>employee</role-name> </auth-constraint> </security-constraint> <!-- Security roles referenced by this web application --> <security-role> <role-name>role1</role-name> </security-role> <security-role> <role-name>employee</role-name> </security-role>
In this example, the security-role element lists all of the security roles used in the application: role1 and employee. This enables the deployer to map all of the roles defined in the application to users and groups defined on the Application Server.
The auth-constraint element specifies the roles (role1, employee) that can access HTTP methods (PUT, DELETE, GET, POST) located in the directory specified by the url-pattern element (/jsp/security/protected/*). You could also have used the @DeclareRoles annotation in the source code to accomplish this task.
The security-role-ref element is used when an application uses the HttpServletRequest.isUserInRole(String role) method. The value of the role-name element must be the String used as the parameter to the HttpServletRequest.isUserInRole(String role) method. The role-link must contain the name of one of the security roles defined in the security-role elements. The container uses the mapping of security-role-ref to security-role when determining the return value of the call.
To map security roles to application server principals and groups, use the security-role-mapping element in the runtime deployment descriptor (DD). The runtime deployment descriptor is an XML file that contains information such as the context root of the web application and the mapping of the portable names of an application’s resources to the Application Server’s resources. The Application Server web application runtime DD is located in /WEB-INF/ along with the web application DD. Runtime deployment descriptors are named sun-web.xml, sun-application.xml, or sun-ejb-jar.xml.
The following example demonstrates how to do this mapping:
<sun-web-app> <security-role-mapping> <role-name>CEO</role-name> <principal-name>smcneely</principal-name> </security-role-mapping> <security-role-mapping> <role-name>Admin</role-name> <group-name>director</group-name> </security-role-mapping> ... </sun-web-app>
A role can be mapped to specific principals, specific groups, or both. The principal or group names must be valid principals or groups in the current default realm. The role-name element must match the role-name in the security-role element of the corresponding application deployment descriptor (web.xml, ejb-jar.xml) or the role name defined in the @DeclareRoles annotation.
Sometimes the role names used in the application are the same as the group names defined on the Application Server. Under these circumstances, you can use the Admin Console to define a default principal to role mapping that apply to the entire Application Server instance. From the Admin Console, select Configuration, then Security, then check the enable box beside Default Principal to Role Mapping. For more information, read the Sun Java System Application Server 9.1 Developer’s Guide or Sun Java System Application Server 9.1 Administration Guide.
In general, security management should be enforced by the container in a manner that is transparent to the web component. The security API described in this section should be used only in the less frequent situations in which the web component methods need to access the security context information.
The HttpServletRequest interface provides the following methods that enable you to access security information about the component’s caller:getRemoteUser: Determines the user name with which the client authenticated. If no user has been authenticated, this method returns null.
isUserInRole: Determines whether a remote user is in a specific security role. If no user has been authenticated, this method returns false. This method expects a String user role-name parameter.
You can use either the @DeclareRoles annotation or the <security-role-ref> element with a <role-name> sub-element in the deployment descriptor to pass the role name to this method. Using security role references is discussed in Declaring and Linking Role References.
getUserPrincipal: Determines the principal name of the current user and returns a java.security.Principal object. If no user has been authenticated, this method returns null.
Your application can make business logic decisions based on the information obtained using these APIs.
The following is a code snippet from an index.jsp file that uses these methods to access security information about the component’s caller.
<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %> <fmt:setBundle basename="LocalStrings"/> <html> <head> <title><fmt:message key="index.jsp.title"/>/title> </head> <body bgcolor="white"> <fmt:message key="index.jsp.remoteuser"/> <b><%= request.getRemoteUser() %> </b><br><br> <% if (request.getUserPrincipal() != null) { %> <fmt:message key="index.jsp.principal"/> <b> <%= request.getUserPrincipal().getName() %></b><br><br> <% } else { %> <fmt:message key="index.jsp.noprincipal"/> <% } %> <% String role = request.getParameter("role"); if (role == null) role = ""; if (role.length() > 0) { if (request.isUserInRole(role)) { %> <fmt:message key="index.jsp.granted"/> <b><%= role %></b><br><br> <% } else { %> <fmt:message key="index.jsp.notgranted"/> <b><%= role %></b><br><br> <% } } %> <fmt:message key="index.jsp.tocheck"/> <form method="GET"> <input type="text" name="role" value="<%= role %>"> </form> </body> </html>
A security role is an application-specific logical grouping of users, classified by common traits such as customer profile or job title. When an application is deployed, these roles are mapped to security identities, such as principals (identities assigned to users as a result of authentication) or groups, in the runtime environment. Based on this mapping, a user with a certain security role has associated access rights to a web application.
The value passed to the isUserInRole method is a String representing the role name of the user. A security role reference defines a mapping between the name of a role that is called from a web component using isUserInRole(String role) and the name of a security role that has been defined for the application. If a <security-role-ref> element is not declared in a deployment descriptor, and the isUserInRole method is called, the container defaults to checking the provided role name against the list of all security roles defined for the web application. Using the default method instead of using the <security-role-ref> element limits your flexibility to change role names in an application without also recompiling the servlet making the call.
For example, during application assembly, the assembler creates security roles for the application and associates these roles with available security mechanisms. The assembler then resolves the security role references in individual servlets and JSP pages by linking them to roles defined for the application. For example, the assembler could map the security role reference cust to the security role with the role name bankCustomer using the <security-role-ref> element of the deployment descriptor.
The preferred method of declaring roles referenced in an application is to use the @DeclareRoles annotation. The following code sample provides an example that specifies that the roles of j2ee and guest will be used in the application, and verifies that the user is in the role of j2ee before printing out Hello World.
import java.io.IOException; import java.io.PrintWriter; import javax.annotation.security.DeclareRoles; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @DeclareRoles({"j2ee", "guest"}) public class Servlet extends HttpServlet { public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<HTML><HEAD><TITLE>Servlet Output</TITLE> </HEAD><BODY>"); if (req.isUserInRole("j2ee") && !req.isUserInRole("guest")) { out.println("Hello World"); } else { out.println("Invalid roles"); } out.println("</BODY></HTML>"); } }
An example of declaring roles referenced in an application using deployment descriptor elements is shown in the following web.xml deployment descriptor snippet:
<servlet> ... <security-role-ref> <role-name>cust</role-name> <role-link>bankCustomer</role-link> </security-role-ref> ... </servlet>
When you use the isUserInRole(String role) method, the String role is mapped to the role name defined in the <role-name> element nested within the <security-role-ref> element. The <role-link> element in the web.xml deployment descriptor must match a <role-name> defined in the <security-role> element of the web.xml deployment descriptor, as shown here:
<security-role> <role-name>bankCustomer</role-name> </security-role>
Web applications are created by application developers who give, sell, or otherwise transfer the application to an application deployer for installation into a runtime environment. Application developers communicate how the security is to be set up for the deployed application declaratively by use of the deployment descriptor mechanism or programmatically by use of annotations. When this information is passed on to the deployer, the deployer uses this information to define method permissions for security roles, set up user authentication, and whether or not to use HTTPS for transport. If you don’t define security requirements, the deployer will have to determine the security requirements independently.
If you specify a value in an annotation, and then explicitly specify the same value in the deployment descriptor, the value in the deployment descriptor overrides any values specified in annotations. If a value for a servlet has not been specified in the deployment descriptor, and a value has been specified for that servlet by means of the use of annotations, the value specified in annotations will apply. The granularity of overriding is on the per-servlet basis.
The web application deployment descriptor may contain an attribute of metadata-complete on the web-app element. The metadata-complete attribute defines whether the web application deployment descriptor is complete, or whether the class files of the JAR file should be examined for annotations that specify deployment information. When the metadata-complete attribute is not specified, or is set to false, the deployment descriptors examine the class files of applications for annotations that specify deployment information. When the metadata-complete attribute is set to true, the deployment descriptor ignores any servlet annotations present in the class files of the application. Thus, deployers can use deployment descriptors to customize or override the values specified in annotations.
Many elements for security in a web application deployment descriptor cannot, as yet, be specified as annotations, therefore, for securing web applications, deployment descriptors are a necessity. However, where possible, annotations are the recommended method for securing web components.
The following sections discuss the use of annotations and deployment descriptor elements to secure web applications:
The Java Metadata Specification (JSR-175), which is part of J2SE 5.0 and greater, provides a means of specifying configuration data in Java code. Metadata in Java code is more commonly referred to in this document as annotations. In Java EE, annotations are used to declare dependencies on external resources and configuration data in Java code without the need to define that data in a configuration file. Several common annotations are specific to specifying security in any Java application. These common annotations are specified in JSR-175, A Metadata Facility for the Java Programming Language, and JSR-250, Common Annotations for the Java Platform. Annotations specific to web components are specified in the Java Servlet 2.5 Specification.
In servlets, you can use the annotations discussed in the following sections to secure a web application:
This annotation is used to define the security roles that comprise the security model of the application. This annotation is specified on a class, and it typically would be used to define roles that could be tested (for example, by calling isUserInRole) from within the methods of the annotated class.
Following is an example of how this annotation would be used. In this example, BusinessAdmin is the only security role specified, but the value of this parameter can include a list of security roles specified by the application.
@DeclareRoles("BusinessAdmin") public class CalculatorServlet { //... }
Specifying @DeclareRoles("BusinessAdmin") is equivalent to defining the following in web.xml:
<web-app> <security-role> <role-name>BusinessAdmin</role-name> </security-role> </web-app>
The syntax for declaring more than one role is as shown in the following example:
@DeclareRoles({"Administrator", "Manager", "Employee"})
This annotation is not used to link application roles to other roles. When such linking is necessary, it is accomplished by defining an appropriate security-role-ref in the associated deployment descriptor, as described in Declaring and Linking Role References.
When a call is made to isUserInRole from the annotated class, the caller identity associated with the invocation of the class is tested for membership in the role with the same name as the argument to isUserInRole. If a security-role-ref has been defined for the argument role-name, the caller is tested for membership in the role mapped to the role-name.
For further details on the @DeclareRoles annotation, refer to JSR–250, Common Annotations for the Java Platform, and Using Enterprise Bean Security Annotations in this tutorial.
The @RunAs annotation defines the role of the application during execution in a Java EE container. It can be specified on a class, allowing developers to execute an application under a particular role. The role must map to the user/group information in the container’s security realm. The value element in the annotation is the name of a security role of the application during execution in a Java EE container. The use of the @RunAs annotation is discussed in more detail in Propagating Security Identity.
The following is an example that uses the @RunAs annotation:
@RunAs("Admin") public class CalculatorServlet { @EJB private ShoppingCart myCart; public void doGet(HttpServletRequest, req, HttpServletResponse res) { //.... myCart.getTotal(); //.... } } //.... }
The @RunAs annotation is equivalent to the run-as element in the deployment descriptor.
Web applications are created by application developers who give, sell, or otherwise transfer the application to an application deployer for installation into a runtime environment. Application developers communicate how the security is to be set up for the deployed application declaratively by use of the deployment descriptor mechanism. A deployment descriptor enables an application’s security structure, including roles, access control, and authentication requirements, to be expressed in a form external to the application.
A web application is defined using a standard Java EE web.xml deployment descriptor. A deployment descriptor is an XML schema document that conveys elements and configuration information for web applications. The deployment descriptor must indicate which version of the web application schema (2.4 or 2.5) it is using, and the elements specified within the deployment descriptor must comply with the rules for processing that version of the deployment descriptor. Version 2.5 of the Java Servlet Specification, which can be downloaded at SRV.13, Deployment Descriptor, contains more information regarding the structure of deployment descriptors.
The following code is an example of the elements in a deployment descriptor that apply specifically to declaring security for web applications or for resources within web applications. This example comes from section SRV.13.5.2, An Example of Security, from the Java Servlet Specification 2.5.
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd" version="2.5"> <display-name>A Secure Application</display-name> <!-- SERVLET --> <servlet> <servlet-name>catalog</servlet-name> <servlet-class>com.mycorp.CatalogServlet</servlet-class> <init-param> <param-name>catalog</param-name> <param-value>Spring</param-value> </init-param> <security-role-ref> <role-name>MGR</role-name> <!-- role name used in code --> <role-link>manager</role-link> </security-role-ref> </servlet> <!-- SECURITY ROLE --> <security-role> <role-name>manager</role-name> </security-role> <servlet-mapping> <servlet-name>catalog</servlet-name> <url-pattern>/catalog/*</url-pattern> </servlet-mapping> <!-- SECURITY CONSTRAINT --> <security-constraint> <web-resource-collection> <web-resource-name>CartInfo</web-resource-name> <url-pattern>/catalog/cart/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>manager</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> <!-- LOGIN CONFIGURATION--> <login-config> <auth-method>BASIC</auth-method> </login-config> </web-app>
As shown in the preceding example, the <web-app> element is the root element for web applications. The <web-app> element contains the following elements that are used for specifying security for a web application:
<security-role-ref>
The security role reference element contains the declaration of a security role reference in the web application’s code. The declaration consists of an optional description, the security role name used in the code, and an optional link to a security role.
The security role name specified here is the security role name used in the code. The value of the role-name element must be the String used as the parameter to the HttpServletRequest.isUserInRole(String role) method. The container uses the mapping of security-role-ref to security-role when determining the return value of the call.
The security role link specified here contains the value of the name of the security role that the user may be mapped into. The role-link element is used to link a security role reference to a defined security role. The role-link element must contain the name of one of the security roles defined in the security-role elements.
For more information about security roles, read Working with Security Roles.
<security-role>
A security role is an abstract name for the permission to access a particular set of resources in an application. A security role can be compared to a key that can open a lock. Many people might have a copy of the key. The lock doesn’t care who you are, only that you have the right key.
The security-role element is used with the security-role-ref element to map roles defined in code to roles defined for the web application. For more information about security roles, read Working with Security Roles.
<security-constraint>
A security constraint is used to define the access privileges to a collection of resources using their URL mapping. Read Specifying Security Constraints for more detail on this element. The following elements can be part of a security constraint:
<web-resource-collection> element: Web resource collections describe a URL pattern and HTTP method pair that identify resources that need to be protected.
<auth-constraint> element: Authorization constraints indicate which users in specified roles are permitted access to this resource collection. The role name specified here must either correspond to the role name of one of the <security-role> elements defined for this web application, or be the specially reserved role name *, which is a compact syntax for indicating all roles in the web application. Role names are case sensitive. The roles defined for the application must be mapped to users and groups defined on the server. For more information about security roles, read Working with Security Roles.
<user-data-constraint> element: User data constraints specify network security requirements, in particular, this constraint specifies how data communicated between the client and the container should be protected. If a user transport guarantee of INTEGRAL or CONFIDENTIAL is declared, all user name and password information will be sent over a secure connection using HTTP over SSL (HTTPS). Network security requirements are discussed in Specifying a Secure Connection.
<login-config>
The login configuration element is used to specify the user authentication method to be used for access to web content, the realm in which the user will be authenticated, and, in the case of form-based login, additional attributes. When specified, the user must be authenticated before access to any resource that is constrained by a security constraint will be granted. The types of user authentication methods that are supported include basic, form-based, digest, and client certificate. Read Specifying an Authentication Mechanism for more detail on this element.
Some of the elements of web application security must be addressed in server configuration files rather than in the deployment descriptor for the web application. Configuring security on the Application Server is discussed in the following sections and books:
The following sections provide more information on deployment descriptor security elements:
Security constraints are a declarative way to define the protection of web content. A security constraint is used to define access privileges to a collection of resources using their URL mapping. Security constraints are defined in a deployment descriptor. The following example shows a typical security constraint, including all of the elements of which it consists:
<security-constraint> <display-name>ExampleSecurityConstraint</display-name> <web-resource-collection> <web-resource-name> ExampleWRCollection </web-resource-name> <url-pattern>/example</url-pattern> <http-method>POST</http-method> <http-method>GET</http-method> </web-resource-collection> <auth-constraint> <role-name>exampleRole</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>
As shown in the example, a security constraint (<security-constraint> in deployment descriptor) consists of the following elements:
Web resource collection (web-resource-collection)
A web resource collection is a list of URL patterns (the part of a URL after the host name and port which you want to constrain) and HTTP operations (the methods within the files that match the URL pattern which you want to constrain (for example, POST, GET)) that describe a set of resources to be protected.
Authorization constraint (auth-constraint)
An authorization constraint establishes a requirement for authentication and names the roles authorized to access the URL patterns and HTTP methods declared by this security constraint. If there is no authorization constraint, the container must accept the request without requiring user authentication. If there is an authorization constraint, but no roles are specified within it, the container will not allow access to constrained requests under any circumstances. The wildcard character * can be used to specify all role names defined in the deployment descriptor. Security roles are discussed in Working with Security Roles.
User data constraint (user-data-constraint)
A user data constraint establishes a requirement that the constrained requests be received over a protected transport layer connection. This guarantees how the data will be transported between client and server. The choices for type of transport guarantee include NONE, INTEGRAL, and CONFIDENTIAL. If no user data constraint applies to a request, the container must accept the request when received over any connection, including an unprotected one. These options are discussed in Specifying a Secure Connection.
Security constraints work only on the original request URI and not on calls made throug a RequestDispatcher (which include <jsp:include> and <jsp:forward>). Inside the application, it is assumed that the application itself has complete access to all resources and would not forward a user request unless it had decided that the requesting user also had access.
Many applications feature unprotected web content, which any caller can access without authentication. In the web tier, you provide unrestricted access simply by not configuring a security constraint for that particular request URI. It is common to have some unprotected resources and some protected resources. In this case, you will define security constraints and a login method, but they will not be used to control access to the unprotected resources. Users won’t be asked to log in until the first time they enter a protected request URI.
The Java Servlet specification defines the request URI as the part of a URL after the host name and port. For example, let’s say you have an e-commerce site with a browsable catalog that you would want anyone to be able to access, and a shopping cart area for customers only. You could set up the paths for your web application so that the pattern /cart/* is protected but nothing else is protected. Assuming that the application is installed at context path /myapp, the following are true:
http://localhost:8080/myapp/index.jsp is not protected.
http://localhost:8080/myapp/cart/index.jsp is protected.
A user will not be prompted to log in until the first time that user accesses a resource in the cart/ subdirectory.
You can create a separate security constraint for different resources within your application. For example, you could allow users with the role of PARTNER access to the POST method of all resources with the URL pattern /acme/wholesale/*, and allow users with the role of CLIENT access to the POST method of all resources with the URL pattern /acme/retail/*. An example of a deployment descriptor that would demonstrate this functionality is the following:
// SECURITY CONSTRAINT #1 <security-constraint> <web-resource-collection> <web-resource-name>wholesale</web-resource-name> <url-pattern>/acme/wholesale/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>PARTNER</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint> // SECURITY CONSTRAINT #2 <security-constraint> <web-resource-collection> <web-resource-name>retail</web-resource-name> <url-pattern>/acme/retail/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>CLIENT</role-name> </auth-constraint> </security-constraint>
When the same url-pattern and http-method occur in multiple security constraints, the constraints on the pattern and method are defined by combining the individual constraints, which could result in unintentional denial of access. Section 12.7.2 of the Java Servlet 2.5 Specification (downloadable from http://jcp.org/en/jsr/detail?id=154) gives an example that illustrates the combination of constraints and how the declarations will be interpreted.
A user data constraint (<user-data-constraint> in the deployment descriptor) requires that all constrained URL patterns and HTTP methods specified in the security constraint are received over a protected transport layer connection such as HTTPS (HTTP over SSL). A user data constraint specifies a transport guarantee (<transport-guarantee> in the deployment descriptor). The choices for transport guarantee include CONFIDENTIAL, INTEGRAL, or NONE. If you specify CONFIDENTIAL or INTEGRAL as a security constraint, that type of security constraint applies to all requests that match the URL patterns in the web resource collection and not just to the login dialog box. The following security constraint includes a transport guarantee:
<security-constraint> <web-resource-collection> <web-resource-name>wholesale</web-resource-name> <url-pattern>/acme/wholesale/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>PARTNER</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>CONFIDENTIAL</transport-guarantee> </user-data-constraint> </security-constraint>
The strength of the required protection is defined by the value of the transport guarantee. Specify CONFIDENTIAL when the application requires that data be transmitted so as to prevent other entities from observing the contents of the transmission. Specify INTEGRAL when the application requires that the data be sent between client and server in such a way that it cannot be changed in transit. Specify NONE to indicate that the container must accept the constrained requests on any connection, including an unprotected one.
The user data constraint is handy to use in conjunction with basic and form-based user authentication. When the login authentication method is set to BASIC or FORM, passwords are not protected, meaning that passwords sent between a client and a server on an unprotected session can be viewed and intercepted by third parties. Using a user data constraint with the user authentication mechanism can alleviate this concern. Configuring a user authentication mechanism is described in Specifying an Authentication Mechanism.
To guarantee that data is transported over a secure connection, ensure that SSL support is configured for your server. If your server is the Sun Java System Application Server, SSL support is already configured. If you are using another server, consult the documentation for that server for information on setting up SSL support. More information on configuring SSL support on the Application Server can be found in Establishing a Secure Connection Using SSL and in the Sun Java System Application Server 9.1 Administration Guide.
Good Security Practice: If you are using sessions, after you switch to SSL you should never accept any further requests for that session that are non-SSL. For example, a shopping site might not use SSL until the checkout page, and then it might switch to using SSL to accept your card number. After switching to SSL, you should stop listening to non-SSL requests for this session. The reason for this practice is that the session ID itself was not encrypted on the earlier communications. This is not so bad when you’re only doing your shopping, but after the credit card information is stored in the session, you don’t want a bad guy trying to fake the purchase transaction against your credit card. This practice could be easily implemented using a filter.
To specify an authentication mechanism for your web application, declare a login-config element in the application deployment descriptor. The login-config element is used to configure the authentication method and realm name that should be used for this application, and the attributes that are needed by the form login mechanism when form-based login is selected. The sub-element auth-method configures the authentication mechanism for the web application. The element content must be either BASIC, DIGEST, FORM, CLIENT-CERT, or a vendor-specific authentication scheme. The realm-name element indicates the realm name to use for the authentication scheme chosen for the web application. The form-login-config element specifies the login and error pages that should be used when FORM based login is specified.
The authentication mechanism you choose specifies how the user is prompted to login. If the <login-config> element is present, and the <auth-method> element contains a value other than NONE, the user must be authenticated before it can access any resource that is constrained by the use of a security-constraint element in the same deployment descriptor (read Specifying Security Constraints for more information on security constraints). If you do not specify an authentication mechanism, the user will not be authenticated.
When you try to access a web resource that is constrained by a security-constraint element, the web container activates the authentication mechanism that has been configured for that resource. To specify an authentication method, place the <auth-method> element between <login-config> elements in the deployment descriptor, like this:
<login-config> <auth-method>BASIC</auth-method> </login-config>
An example of a deployment descriptor that constrains all web resources for this application (in italics below) and requires HTTP basic authentication when you try to access that resource (in bold below) is shown here:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>basicauth</display-name> <servlet> <display-name>index</display-name> <servlet-name>index</servlet-name> <jsp-file>/index.jsp</jsp-file> </servlet> <security-role> <role-name>loginUser</role-name> </security-role> <security-constraint> <display-name>SecurityConstraint1</display-name> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>loginUser</role-name> </auth-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> </login-config> </web-app>
Before you can authenticate a user, you must have a database of user names, passwords, and roles configured on your web or application server. For information on setting up the user database, refer to Managing Users and Groups on the Application Server and the Sun Java System Application Server 9.1 Administration Guide.
The authentication mechanisms are discussed further in the following sections:
HTTP Basic Authentication requires that the server request a user name and password from the web client and verify that the user name and password are valid by comparing them against a database of authorized users. When basic authentication is declared, the following actions occur:
A client requests access to a protected resource.
The web server returns a dialog box that requests the user name and password.
The client submits the user name and password to the server.
The server authenticates the user in the specified realm and, if successful, returns the requested resource.
Figure 30–2 shows what happens when you specify HTTP basic authentication.
The following example shows how to specify basic authentication in your deployment descriptor:
<login-config> <auth-method>BASIC</auth-method> </login-config>
HTTP basic authentication is not a secure authentication mechanism. Basic authentication sends user names and passwords over the Internet as text that is Base64 encoded, and the target server is not authenticated. This form of authentication can expose user names and passwords. If someone can intercept the transmission, the user name and password information can easily be decoded. However, when a secure transport mechanism, such as SSL, or security at the network level, such as the IPSEC protocol or VPN strategies, is used in conjunction with basic authentication, some of these concerns can be alleviated.
Example: Basic Authentication with JAX-WS is an example application that uses HTTP basic authentication in a JAX-WS service. Example: Using Form-Based Authentication with a JSP Page can be easily modified to demonstrate basic authentication. To do so, replace the text between the <login-config> elements with those shown in this section.
Form-based authentication allows the developer to control the look and feel of the login authentication screens by customizing the login screen and error pages that an HTTP browser presents to the end user. When form-based authentication is declared, the following actions occur:
A client requests access to a protected resource.
If the client is unauthenticated, the server redirects the client to a login page.
The client submits the login form to the server.
The server attempts to authenticate the user.
If authentication succeeds, the authenticated user’s principal is checked to ensure it is in a role that is authorized to access the resource. If the user is authorized, the server redirects the client to the resource using the stored URL path.
If authentication fails, the client is forwarded or redirected to an error page.
Figure 30–3 shows what happens when you specify form-based authentication.
The following example shows how to declare form-based authentication in your deployment descriptor:
<login-config> <auth-method>FORM</auth-method> <realm-name>file</realm-name> <form-login-config> <form-login-page>/logon.jsp</form-login-page> <form-error-page>/logonError.jsp</form-error-page> </form-login-config> </login-config>
The login and error page locations are specified relative to the location of the deployment descriptor. Examples of login and error pages are shown in Creating the Login Form and the Error Page.
Form-based authentication is not particularly secure. In form-based authentication, the content of the user dialog box is sent as plain text, and the target server is not authenticated. This form of authentication can expose your user names and passwords unless all connections are over SSL. If someone can intercept the transmission, the user name and password information can easily be decoded. However, when a secure transport mechanism, such as SSL, or security at the network level, such as the IPSEC protocol or VPN strategies, is used in conjunction with form-based authentication, some of these concerns can be alleviated.
The section Example: Using Form-Based Authentication with a JSP Page is an example application that uses form-based authentication.
When creating a form-based login, be sure to maintain sessions using cookies or SSL session information.
As shown in Form-Based Authentication, for authentication to proceed appropriately, the action of the login form must always be j_security_check. This restriction is made so that the login form will work no matter which resource it is for, and to avoid requiring the server to specify the action field of the outbound form. The following code snippet shows how the form should be coded into the HTML page:
<form method="POST" action="j_security_check"> <input type="text" name="j_username"> <input type="password" name="j_password"> </form>
HTTPS Client Authentication requires the client to possess a Public Key Certificate (PKC). If you specify client authentication, the web server will authenticate the client using the client’s public key certificate.
HTTPS Client Authentication is a more secure method of authentication than either basic or form-based authentication. It uses HTTP over SSL (HTTPS), in which the server authenticates the client using the client’s Public Key Certificate (PKC). Secure Sockets Layer (SSL) technology provides data encryption, server authentication, message integrity, and optional client authentication for a TCP/IP connection. You can think of a public key certificate as the digital equivalent of a passport. It is issued by a trusted organization, which is called a certificate authority (CA), and provides identification for the bearer.
Before using HTTP Client Authentication, you must make sure that the following actions have been completed:
Make sure that SSL support is configured for your server. If your server is the Sun Java System Application Server 9.1, SSL support is already configured. If you are using another server, consult the documentation for that server for information on setting up SSL support. More information on configuring SSL support on the application server can be found in Establishing a Secure Connection Using SSL and the Sun Java System Application Server 9.1 Administration Guide.
Make sure the client has a valid Public Key Certificate. For more information on creating and using public key certificates, read Working with Digital Certificates.
The following example shows how to declare HTTPS client authentication in your deployment descriptor:
<login-config> <auth-method>CLIENT-CERT</auth-method> </login-config>
With mutual authentication, the server and the client authenticate one another. There are two types of mutual authentication:
Certificate-based mutual authentication (see Figure 30–4)
User name- and password-based mutual authentication (see Figure 30–5)
When using certificate-based mutual authentication, the following actions occur:
A client requests access to a protected resource.
The web server presents its certificate to the client.
The client verifies the server’s certificate.
If successful, the client sends its certificate to the server.
The server verifies the client’s credentials.
If successful, the server grants access to the protected resource requested by the client.
Figure 30–4 shows what occurs during certificate-based mutual authentication.
In user name- and password-based mutual authentication, the following actions occur:
A client requests access to a protected resource.
The web server presents its certificate to the client.
The client verifies the server’s certificate.
If successful, the client sends its user name and password to the server, which verifies the client’s credentials.
If the verification is successful, the server grants access to the protected resource requested by the client.
Figure 30–5 shows what occurs during user name- and password-based mutual authentication.
Like HTTP basic authentication, HTTP Digest Authentication authenticates a user based on a user name and a password. However, the authentication is performed by transmitting the password in an encrypted form which is much more secure than the simple Base64 encoding used by basic authentication. Digest authentication is not currently in widespread use, and is not implemented in the Application Server, therefore, there is no further discussion of it in this document.
There are several ways in which you can secure web applications. These include the following options:
You can define a user authentication method for an application in its deployment descriptor. Authentication verifies the identity of a user, device, or other entity in a computer system, usually as a prerequisite to allowing access to resources in a system. When a user authentication method is specified for an application, the web container activates the specified authentication mechanism when you attempt to access a protected resource.
The options for user authentication methods are discussed in Specifying an Authentication Mechanism. All of the example security applications use a user authentication method.
You can define a transport guarantee for an application in its deployment descriptor. Use this method to run over an SSL-protected session and ensure that all message content is protected for confidentiality or integrity. The options for transport guarantees are discussed in Specifying a Secure Connection.
When running over an SSL-protected session, the server and client can authenticate one another and negotiate an encryption algorithm and cryptographic keys before the application protocol transmits or receives its first byte of data.
SSL technology allows web browsers and web servers to communicate over a secure connection. In this secure connection, the data is encrypted before being sent, and then is decrypted upon receipt and before processing. Both the browser and the server encrypt all traffic before sending any data. For more information, see Establishing a Secure Connection Using SSL.
Digital certificates are necessary when running HTTP over SSL (HTTPS). The HTTPS service of most web servers will not run unless a digital certificate has been installed. Digital certificates have already been created for the Application Server.
The following examples use annotations, programmatic security, and/or declarative security to demonstrate adding security to existing web applications:
The following examples demonstrate adding basic authentication to an EJB endpoint or enterprise bean:
This example discusses how to use form-based authentication with a basic JSP page. With form-based authentication, you can customize the login screen and error pages that are presented to the web client for authentication of their user name and password. When a user submits their name and password, the server determines if the user name and password are those of an authorized user and, if authorized, sends the requested web resource. If the topic of authentication is new to you, please refer to the section Specifying an Authentication Mechanism.
In general, the following steps are necessary for adding form-based authentication to an unsecured JSP page, such as the one described in Web Modules. In the example application included with this tutorial, many of these steps have been completed for you and are listed here simply to show what needs to be done should you wish to create a similar application. The completed version of this example application can be found in the directory tut-install/javaeetutorial5/examples/web/hello1_formauth/.
The following steps describe how to set up your system for running the example applications, describe the sample application, and provide the steps for compiling, packaging, deploying, and testing the example application.
If you have not already done so, set up your system so that the Ant tool and/or NetBeans IDE will run properly. To do this, follow the instructions in Building the Examples. This step is necessary to set the properties that are specific to your installation of the Application Server and Java EE 5 Tutorial.
If you have not already done so, add an authorized user to the Application Server. For this example, add users to the file realm of the Application Server and assign the user to the group user. This topic is discussed more in Adding Authorized Roles and Users.
Create a web module as described in Web Modules. The subsequent steps discuss adding security to this basic application. The resulting application is found in the directory tut-install/javaeetutorial5/examples/web/hello1_formauth/.
Create the login form and login error form pages. Files for the example application can be viewed at tut-install/javaeetutorial5/examples/web/hello1_formauth/web. These pages are discussed in Creating the Login Form and the Error Page.
Create a web.xml deployment descriptor and add the appropriate security elements (the application on which this section is based did not originally require a deployment descriptor.) The deployment descriptor for the example application can be viewed at tut-install/javaeetutorial5/examples/hello1_formauth/web/WEB-INF. The security elements for the web.xml deployment descriptor are described in Specifying a Security Constraint.
Map the role name defined for this resource (loginUser) to a group of users defined on the Application Server. For more information on how to do this, read Mapping Application Roles to Application Server Groups.
Build, package, deploy, and run the web application by following the steps in Building, Packaging, and Deploying the Form-Based Authentication Example Using NetBeans IDE or Building, Packaging, and Deploying the Form-Based Authentication Example Using Ant.
Test the web client, following the steps in Testing the Form-Based Authentication Web Client.
The web client in this example is a standard JSP page, and annotations are not used in JSP pages because JSP pages are compiled as they are presented to the browser. Therefore, none of the code that adds form-based authentication to the example is included in the web client. The code for the JSP page used in this example, hello1_formauth/web/index.jsp, is exactly the same as the code used for the unsecured JSP page from the example application at tut-install/javaeetutorial5/examples/web/hello1/web/index.jsp.
The information that adds form-based authentication to this example is specified in the deployment descriptor. This information is discussed in Specifying a Security Constraint.
When using form-based login mechanisms, you must specify a page that contains the form you want to use to obtain the user name and password, as well as which page to display if login authentication fails. This section discusses the login form and the error page used in this example. The section Specifying a Security Constraint shows how you specify these pages in the deployment descriptor.
The login page can be an HTML page, a JSP page, or a servlet, and it must return an HTML page containing a form that conforms to specific naming conventions (see the Java Servlet 2.5 specification for more information on these requirements). To do this, include the elements that accept user name and password information between <form></form> tags in your login page. The content of an HTML page, JSP page, or servlet for a login page should be coded as follows:
<form method=post action="j_security_check" > <input type="text" name= "j_username" > <input type="password" name= "j_password" > </form>
The full code for the login page used in this example can be found at tut-install/javaeetutorial5/examples/web/hello1_formauth/web/logon.jsp. An example of the running login form page is shown later in Figure 30–6. Here is the code for this page:
<html> <head> <title>Login Page</title> </head> <h2>Hello, please log in:</h2> <br><br> <form action="j_security_check" method=post> <p><strong>Please Enter Your User Name: </strong> <input type="text" name="j_username" size="25"> <p><p><strong>Please Enter Your Password: </strong> <input type="password" size="15" name="j_password"> <p><p> <input type="submit" value="Submit"> <input type="reset" value="Reset"> </form> </html>
The login error page is displayed if the user enters a user name and password combination that is not authorized to access the protected URI. For this example, the login error page can be found at tut-install/javaeetutorial5/examples/web/hello1_formauth/web/logonError.jsp. For this example, the login error page explains the reason for receiving the error page and provides a link that will allow the user to try again. Here is the code for this page:
<html> <head> <title>Login Error</title> </head> <body> <c:url var="url" value="/index.jsp"/> <h2>Invalid user name or password.</h2> <p>Please enter a user name or password that is authorized to access this application. For this application, this means a user that has been created in the <code>file</code> realm and has been assigned to the <em>group</em> of <code>user</code>. Click here to <a href="${url}">Try Again</a></p> </body> </html>
This example takes a very simple JSP page-based web application and adds form-based security to this application. The JSP page is exactly the same as the JSP page used in the example described in Web Modules. All security for this example is declared in the deployment descriptor for the application. A security constraint is defined in the deployment descriptor that tells the server to send a login form to collect user data, verify that the user is authorized to access the application, and, if so, display the JSP page to the user.
If this client were a web service endpoint and not a JSP page, you could use annotations to declare security roles and to specify which roles were allowed access to which methods. However, there is no resource injection in JSP pages, so you cannot use annotations and must use the equivalent deployment descriptor elements.
Deployment descriptor elements are described in Declaring Security Requirements in a Deployment Descriptor.
The following sample code shows the deployment descriptor used in this example of form-based login authentication, which can be found in tut-install/javaeetutorial5/examples/web/hello1_formauth/web/WEB-INF/web.xml.
<!-- FORM-BASED LOGIN AUTHENTICATION EXAMPLE --> <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>hello1_formauth</display-name> <servlet> <display-name>index</display-name> <servlet-name>index</servlet-name> <jsp-file>/index.jsp</jsp-file> </servlet> <security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>loginUser</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>FORM</auth-method> <form-login-config> <form-login-page>/logon.jsp</form-login-page> <form-error-page>/logonError.jsp</form-error-page> </form-login-config> </login-config> <security-role> <role-name>loginUser</role-name> </security-role> </web-app>
More description of the elements that declare security in a deployment descriptor can be found in Specifying Security Constraints.
Passwords are not protected for confidentiality with HTTP basic or form-based authentication, meaning that passwords sent between a client and a server on an unprotected session can be viewed and intercepted by third parties. To overcome this limitation, you can run these authentication protocols over an SSL-protected session and ensure that all message content is protected for confidentiality.
A <transport-guarantee> element indicates whether or not the protected resources should travel over protected transport. For simplicity, this example does not require protected transport, but in a real world application, you would want to set this value to CONFIDENTIAL to ensure that the user name and password are not observed during transmission. When running on protected transport, you can run the application over the secure SSL protocol, https, and specify the secure port where your SSL connector is created (the default for the Application Server is 8181). If you do not specify the HTTPS protocol, the server will automatically redirect the application to the secure port.
To authenticate a user and allow that user access to protected resources on the Application Server, you must link the roles defined in the application to the users defined for the Application Server.
An application may define security roles, which are a logical grouping of users, classified by common traits such as customer profile or job title.
The Application Server has multiple realms, each of which generally includes a database of authorized users, their passwords, and one or more logical groups to which the each user belongs.
When an application is deployed, the application-specific security roles are mapped to security identities in the runtime environment, such as principals (identities assigned to users as a result of authentication) or groups. Based on this mapping, a user who has been assigned a certain security role has associated access rights to a web application deployed onto a server.
As shown in the deployment descriptor for this example application, the security constraint specifies that users assigned to the role of loginUser are authorized to access any of the files in the hello1_formauth application. In this example, when a resource that is constrained by this same security constraint is accessed, for example, hello1_formauth/web/index.jsp, the Application Server sends the login form, receives the login information, and checks to see if the user is in a group that has been mapped to the role of loginUser. If the user name and password are those of an authorized user, access to the resource is granted to the requester.
To set up users for this example application, follow these steps:
Using the Admin Console, create a user in the file realm of the Application Server and assign that user to the group user. Make sure to note the user name and password that you enter in this step so that you can use it for testing the application later (these fields are case-sensitive). If you need help with the steps required to accomplish this task, read Managing Users and Groups on the Application Server for more information.
Map the application security role of loginUser to the group of user that has been configured on the Application Server. For more information on how to do this mapping, read Mapping Application Roles to Application Server Groups.
Map the role of loginUser defined in the application to the group of user defined on the Application Server by adding a security-role-mapping element to the sun-web.xml runtime deployment descriptor file. To deploy a WAR on the Application Server, the WAR file must contain a runtime deployment descriptor. The runtime deployment descriptor is an XML file that contains information such as the context root of the web application and the mapping of the portable names of an application’s resources to the Application Server’s resources.
The runtime deployment descriptor for this example, tut-install/javaeetutorial5/examples/web/hello1_formauth/web/WEB-INF/sun-web.xml, looks like this:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd"> <sun-web-app> <context-root>/hello1_formauth </context-root> <security-role-mapping> <role-name>loginUser</role-name> <group-name>user</group-name> </security-role-mapping> </sun-web-app>
To build, package, and deploy this application using NetBeans IDE, follow these steps:
Follow the instructions in Building the Examples if you have not already done so. This step is necessary to provide the Ant targets with the location of your tutorial and Application Server installations.
Add users to the file realm of the Application Server as described in Adding Authorized Roles and Users if you have not already done so.
Open the project in NetBeans IDE by selecting File->Open Project.
Browse to the tut-install/javaeetutorial5/examples/web/hello1_formauth/ directory.
Make sure that Open as Main Project is selected.
Select Open Project.
If you are prompted to regenerate the build-impl.xml file, select the Regenerate button.
Right-click hello1_formauth in the Projects pane, then select Clean and Build.
Right-click hello1_formauth in the Projects pane, then select Undeploy and Deploy.
Follow the steps in Testing the Form-Based Authentication Web Client.
To build, package, and deploy this application using the Ant tool, follow these steps:
Follow the instructions in Building the Examples if you have not already done so. This step is necessary to provide the Ant targets with the location of your tutorial and Application Server installations.
Add users to the file realm of the Application Server as described in Adding Authorized Roles and Users if you have not already done so.
From a terminal window or command prompt, change to the tut-install/javaeetutorial5/examples/web/hello1_formauth/ directory.
Enter the following command at the terminal window or command prompt:
ant |
This target will spawn any necessary compilations, copy files to the tut-install/javaeetutorial5/examples/web/hello1_formauth/build/ directory, create the WAR file, and copy it to the tut-installjavaeetutorial5/examples/web/hello1_formauth/dist/ directory.
Deploy the WAR named hello1_formauth.war onto the Application Server using Ant by entering the following command at the terminal window or command prompt:
ant deploy |
Follow the steps in Testing the Form-Based Authentication Web Client.
To run the web client, follow these steps:
Open a web browser.
Enter the following URL in your web browser:
http://localhost:8080/hello1_formauth
If you set the transport guarantee to CONFIDENTIAL as discussed in Protecting Passwords with SSL, you must load the application in a web browser using https for the protocol, the HTTPS port that you specified during installation for the port (by default this port is 8181), and the context name for the application you wish to run. For the form-based authentication example, you could run the example using the following URL: https://localhost:8181/hello1_formauth.
The login form displays in the browser, as shown in Figure 30–6.
Enter a user name and password combination that corresponds to a user that has already been created in the file realm of the Application Server and has been assigned to the group of user, as discussed in Adding Authorized Roles and Users.
Click the Submit button. Form-based authentication is case-sensitive for both the user name and password, so enter the user name and password exactly as defined for the Application Server.
If you entered My_Name as the name and My_Pwd for the password, the server returns the requested resource if all of the following conditions are met:
There is a user defined for the Application Server with the user name of My_Name.
The user with the user name of My_Name has a password of My_Pwd defined for the Application Server.
The user My_Name with the password My_Pwd is assigned to the group of user on the Application Server.
The role of loginUser, as defined for the application, is mapped to the group of user, as defined for the Application Server.
When these conditions are met, and the server has authenticated the user, the application will display as shown in Figure 30–7.
Enter your name and click the Submit button. Because you have already been authorized, the name you enter in this step does not have any limitations. You have unlimited access to the application now.
The application responds by saying “Hello” to you, as shown in Figure 30–8.
For repetitive testing of this example, you may need to close and reopen your browser. You should also run the ant clean and ant undeploy commands to ensure a fresh build if using the Ant tool, or select Clean and Build then Undeploy and Deploy if using NetBeans IDE.
This example discusses how to use basic authentication with a servlet. With basic authentication of a servlet, the web browser presents a standard login dialog that is not customizable. When a user submits their name and password, the server determines if the user name and password are those of an authorized user and sends the requested web resource if the user is authorized to view it. If the topic of authentication is new to you, please refer to the section Specifying an Authentication Mechanism.
In general, the following steps are necessary for adding basic authentication to an unsecured servlet, such as the one described in Web Modules. In the example application included with this tutorial, many of these steps have been completed for you and are listed here simply to show what needs to be done should you wish to create a similar application. The completed version of this example application can be found in the directory tut-install/javaeetutorial5/examples/web/hello2_basicauth/.
The following steps describe how to set up your system for running the example applications, describe the sample application, and provide the steps for compiling, packaging, deploying, and testing the example application.
If you have not already done so, set up your system so that the Ant tool and/or NetBeans IDE will run properly. To do this, follow the instructions in Building the Examples. This step is necessary to set the properties that are specific to your installation of the Application Server and Java EE 5 Tutorial.
If you have not already done so, add an authorized user to the Application Server. For this example, add users to the file realm of the Application Server and assign the user to the group user. This topic is discussed more in Adding Authorized Roles and Users.
Create a web module as described in Web Modules for the servlet example, hello2. The subsequent steps discuss adding security to this basic application. The files for this example application are in tut-install/javaeetutorial5/examples/web/hello2_basicauth/.
Declare the roles that will be used in this application. For this example, this is done by adding the @DeclareRoles annotation to GreetingServlet.java. This code is shown in Declaring Security Roles.
Add the appropriate security elements to the web.xml deployment descriptor. The deployment descriptor for the example application can be viewed at tut-install/javaeetutorial5/examples/web/hello2_basicauth/web/WEB-INF/web.xml. The security elements are described in Specifying the Security Constraint.
Map the role name defined for this resource (helloUser) to a group of users defined on the Application Server. For more information on how to do this, read Mapping Application Roles to Application Server Groups.
Build, package, and deploy the web application by following the steps in Building, Packaging, and Deploying the Servlet Basic Authentication Example Using NetBeans IDE or Building, Packaging, and Deploying the Servlet Basic Authentication Example Using Ant.
Run the web application by following the steps described in Running the Basic Authentication Servlet.
If you have any problems running this example, refer to the troubleshooting tips in Troubleshooting the Basic Authentication Example.
There are two annotations that can be used with servlets: @DeclareRoles and @RunAs. In this example, the @DeclareRoles annotation is used to specify which roles are referenced in this example.
The following section of the tut-install/javaeetutorial5/examples/web/hello2_basicauth/src/servlets/GreetingServlet.java file contains the code necessary to declare that the role of helloUser is used in this application:
package servlets; import java.io.*; import java.util.*; import java.sql.*; import javax.servlet.*; import javax.servlet.http.*; import javax.annotation.security.DeclareRoles; /** * This is a simple example of an HTTP Servlet that can only be accessed * by an authenticated user. It responds to the GET * method of the HTTP protocol. */ @DeclareRoles("helloUser") public class GreetingServlet extends HttpServlet { public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
You could also declare security roles using the <security-role> element in the deployment descriptor. If you prefer to declare security roles this way, read Declaring Roles Using Deployment Descriptor Elements.
This example takes a very simple servlet-based web application and adds basic authentication to this application. The servlet is basically the same as the servlet used in the example described in Web Modules, with the exception of the annotations added and discussed in Declaring Security Roles.
The security constraint for this example is declared in the application deployment descriptor. The security constraint tells the server or browser to perform the following tasks:
Send a standard login dialog to collect user name and password data
Verify that the user is authorized to access the application
If authorized, display the servlet to the user
Deployment descriptors elements are described in Declaring Security Requirements in a Deployment Descriptor.
The following sample code shows the security elements for the deployment descriptor used in this example of basic authentication, which can be found in tut-install/javaeetutorial5/examples/web/hello2_basicauth/web/WEB-INF/web.xml.
<security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/greeting</url-pattern> </web-resource-collection> <auth-constraint> <role-name>helloUser</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>file</realm-name> </login-config>
More description of the elements that declare security in a deployment descriptor can be found in Specifying Security Constraints.
Passwords are not protected for confidentiality with HTTP basic or form-based authentication, meaning that passwords sent between a client and a server on an unprotected session can be viewed and intercepted by third parties. To overcome this limitation, you can run these authentication protocols over an SSL-protected session and ensure that all message content is protected for confidentiality.
A <transport-guarantee> element indicates whether or not the protected resources should travel over protected transport. For simplicity, this example does not require protected transport, but in a real world application, you would want to set this value to CONFIDENTIAL to ensure that the user name and password are not observed during transmission. When running on protected transport, you need to use the secure SSL protocol, https, and specify the secure port where your SSL connector is created (the default for the Application Server is 8181).
To authenticate a user and allow that user access to protected resources on the Application Server, you must link the roles defined in the application to the users defined for the Application Server.
A security role, which is defined at the application level, is a logical grouping of users, classified by common traits such as customer profile or job title.
The Application Server has multiple realms, each of which generally includes a database of authorized users, their passwords, and one or more logical groups to which the each user belongs.
When an application is deployed, the application-specific security roles are mapped to security identities in the runtime environment, such as principals (identities assigned to users as a result of authentication) or groups. Based on this mapping, a user who has been assigned a certain security role has associated access rights to a web application deployed onto a server.
As shown in the deployment descriptor for this example application, the security constraint specifies that users assigned to the role of helloUser are authorized to access the URL pattern /greeting. In this example, when this resource (because it is constrained by a security constraint) is accessed, the Application Server sends a default login dialog, receives the login information, and checks to see if the user is in a group that has been mapped to the role of helloUser. If the user name and password are those of an authorized user, access to the resource is granted to the requester.
To set up users for this example application, follow these steps:
If you have not already done so, create a user in the file realm of the Application Server and assign that user to the group user. Make sure to note the user name and password that you enter in this step so that you can use it for testing the application later. If you need help with the steps required to accomplish this task, read Managing Users and Groups on the Application Server for more information.
Map the application security role of helloUser to the group of user that has been configured on the Application Server. For more information on how to do this mapping, read Mapping Application Roles to Application Server Groups.
Map the role of helloUser defined in the application to the group of user defined on the Application Server by adding a security-role-mapping element to the sun-web.xml runtime deployment descriptor file. The runtime deployment descriptor is an XML file that contains information such as the context root of the web application and the mapping of the portable names of an application’s resources to the Application Server’s resources.
The runtime deployment descriptor for this example, tut-install/javaeetutorial5/examples/web/hello2_basicauth/web/WEB-INF/sun-web.xml, looks like this:
<sun-web-app> <context-root>/hello2_basicauth</context-root> <security-role-mapping> <role-name>helloUser</role-name> <group-name>user</group-name> </security-role-mapping> </sun-web-app>
To build, package, and deploy the web/hello2_basicauth example application using NetBeans IDE, follow these steps:
If you have not already done so, follow the instructions in Building the Examples. This step is necessary to provide the Ant targets with the location of your tutorial and Application Server installations.
If you have not already done so, add authorized users to the file realm of the Application Server as described in Adding Authorized Roles and Users.
Open the project in NetBeans IDE by selecting File->Open Project.
Browse to the tut-installjavaeetutorial5/examples/web/hello2_basicauth/ directory.
Make sure that Open as Main Project is selected.
Select Open Project.
Right-click hello2_basicauth in the Projects pane, then select Clean and Build.
Right-click hello2_basicauth in the Projects pane, then select Undeploy and Deploy.
To run the servlet, follow the steps in Running the Basic Authentication Servlet.
To build, package, and deploy the web/hello2_basicauth example using the Ant tool, follow these steps:
If you have not already done so, follow the instructions in Building the Examples. This step is necessary to provide the Ant targets with the location of your tutorial and Application Server installations.
If you have not already done so, add authorized users to the file realm of the Application Server as described in Adding Authorized Roles and Users.
From a terminal window or command prompt, change to the tut-install/javaeetutorial5/examples/web/hello2_basicauth/ directory.
Build and package the web application by entering the following command at the terminal window or command prompt:
ant |
This command uses web.xml and sun-web.xml files, located in the tut-install/javaeetutorial5/examples/web/hello2_basicauth/web/WEB-INF/ directory.
To deploy the example using Ant, enter the following command at the terminal window or command prompt:
ant deploy |
The deploy target in this case gives you an incorrect URL to run the application. To run the application, please use the URL shown in Running the Basic Authentication Servlet.
To run the web application, follow the steps in Running the Basic Authentication Servlet.
To run the web client, follow these steps:
Open a web browser.
Enter the following URL in your web browser:
http://localhost:8080/hello2_basicauth/greeting
If you set the transport guarantee to CONFIDENTIAL as discussed in Protecting Passwords with SSL, you must load the application in a web browser using https for the protocol, the HTTPS port that you specified during installation for the port (by default this port is 8181), and the context name for the application you wish to run. For the basic authentication example, you could run the example using the following URL: https://localhost:8181/hello2_basicauth/greeting.
A default login form displays. Enter a user name and password combination that corresponds to a user that has already been created in the file realm of the Application Server and has been assigned to the group of user, as discussed in Adding Authorized Roles and Users.
Basic authentication is case-sensitive for both the user name and password, so enter the user name and password exactly as defined for the Application Server.
If you entered My_Name as the name and My_Pwd for the password, the server returns the requested resource if all of the following conditions are met:
There is a user defined for the Application Server with the user name of My_Name.
The user with the user name of My_Name has a password of My_Pwd defined for the Application Server.
The user My_Name with the password My_Pwd is assigned to the group of user on the Application Server.
The role of helloUser, as defined for the application, is mapped to the group of user, as defined for the Application Server.
When these conditions are met, and the server has authenticated the user, the application will display as shown in Figure 30–9.
Enter your name and click the Submit button. Because you have already been authorized, the name you enter in this step does not have any limitations. You have unlimited access to the application now.
The application responds by saying “Hello” to you, as shown in Figure 30–10.
For repetitive testing of this example, you may need to close and reopen your browser. You should also run the ant clean and ant undeploy targets or the NetBeans IDE Clean and Build option to get a fresh start.
When doing iterative development with this web application, follow these steps if you are using NetBeans IDE:
Close your web browser.
Clean and recompile the files from the previous build by right-clicking hello2_basicauth and selecting Clean and Build.
Redeploy the application by right-clicking hello2_basicauth and selecting Undeploy and Deploy.
Open your web browser and reload the following URL:
http://localhost:8080/hello2_basicauth/greeting
Follow these steps if you are using the Ant tool:
Close your web browser.
Undeploy the web application. To undeploy the application, use the following command in the directory:
ant undeploy |
Clean out files from the previous build, using the following command:
ant clean |
Recompile, repackage, and redeploy the application, using the following commands:
ant ant deploy |
Open your web browser and reload the following URL:
http://localhost:8080/hello2_basicauth/greeting
This section discusses how to configure a JAX-WS-based web service for HTTP basic authentication. When a service that is constrained by HTTP basic authentication is requested, the server requests a user name and password from the client and verifies that the user name and password are valid by comparing them against a database of authorized users.
If the topic of authentication is new to you, refer to the section titled Specifying an Authentication Mechanism. For an explanation of how basic authentication works, see Figure 30–2.
For this tutorial, you will add the security elements to the JAX-WS service and client; build, package, and deploy the service; and then build and run the client application.
This example service was developed by starting with an unsecured service, helloservice, which can be found in the directory tut-install/javaeetutorial5/examples/jaxws/helloservice and is discussed in Creating a Simple Web Service and Client with JAX-WS. You build on this simple application by adding the necessary elements to secure the application using basic authentication. The example client used in this application can be found at tut-install/javaeetutorial5/examples/jaxws/simpleclient-basicauth, which only varies from the original simpleclient application in that it uses the helloservice-basicauth endpoint instead of the helloservice endpoint. The completed version of the secured service can be found at tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth.
In general, the following steps are necessary to add basic authentication to a JAX-WS web service. In the example application included with this tutorial, many of these steps have been completed for you and are listed here simply to show what needs to be done should you wish to create a similar application.
Create an application like the one in Creating a Simple Web Service and Client with JAX-WS. The example in this tutorial starts with that example and demonstrates adding basic authentication of the client to this application. The completed version of this application is located in the directories tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth and tut-install/javaeetutorial5/examples/jaxws/simpleclient-basicauth.
If the port value was set to a value other than the default (8080), follow the instructions in Setting the Port to update the example files to reflect this change.
If you have not already done so, follow the steps in Building the Examples for information on setting up your system to run the example.
If you have not already done so, add a user to the file realm and specify user for the group of this new user. Write down the user name and password so that you can use them for testing this application in a later step. If you have not already completed this step, refer to the section Managing Users and Groups on the Application Server for instructions.
Modify the source code for the service, Hello.java, to specify which roles are authorized to access the sayHello (String name) method. This step is discussed in Annotating the Service.
Add security elements that specify that basic authentication is to be performed to the application deployment descriptor, web.xml. This step is discussed in Adding Security Elements to the Deployment Descriptor.
Modify the runtime deployment descriptor, sun-web.xml, to map the role used in this application (basicUser) to a group defined on the Application Server (user). This step is discussed in Linking Roles to Groups.
Build, package, and deploy the web service. See Building and Deploying helloservice with Basic Authentication Using NetBeans IDE or Building and Deploying helloservice with Basic Authentication Using Ant for the steps to accomplish this.
Build and run the client application. See Building and Running the helloservice Client Application with Basic Authentication Using NetBeans IDE or Building and Running the helloservice Client Application with Basic Authentication Using Ant for the steps to accomplish this.
In this example, annotations are used to specify which users are authorized to access which methods of this service. In this simple example, the @RolesAllowed annotation is used to specify that users in the application role of basicUser are authorized access to the sayHello(String name) method. This application role must be linked to a group of users on the Application Server. Linking the roles to groups is discussed in Linking Roles to Groups.
The source code for the original /helloservice application was modified as shown in the following code snippet (modifications in bold). This file can be found in the following location:
tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth/src/java/helloservice/ basicauth/endpoint/Hello.java |
The code snippet is as follows:
package helloservice.basicauth.endpoint; import javax.jws.WebMethod; import javax.jws.WebService; import javax.annotation.security.RolesAllowed; @WebService() public class Hello { private String message = new String("Hello, "); @WebMethod() @RolesAllowed("basicUser") public String sayHello(String name) { return message + name + "."; } }
The @RolesAllowed annotation specifies that only users in the role of basicUser will be allowed to access the sayHello (String name) method. An @RolesAllowed annotation implicitly declares a role that will be referenced in the application, therefore, no @DeclareRoles annotation is required.
To enable basic authentication for the service, add security elements to the application deployment descriptor, web.xml. The security elements that need to be added to the deployment descriptor include the <security-constraint> and <login-config>elements. These security elements are discussed in more detail in Declaring Security Requirements in a Deployment Descriptor and in the Java Servlet Specification. Code in bold is added to the original deployment descriptor to enable HTTP basic authentication. The resulting deployment descriptor is located in tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth/web/WEB-INF/web.xml.
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5" xmlns:xsi="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>HelloService</display-name> <listener> <listener-class> com.sun.xml.ws.transport.http.servlet.WSServletContextListener </listener-class> </listener> <servlet> <display-name>HelloService</display-name> <servlet-name>HelloService</servlet-name> <servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloService</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <security-constraint> <display-name>SecurityConstraint</display-name> <web-resource-collection> <web-resource-name>WRCollection</web-resource-name> <url-pattern>/hello</url-pattern> </web-resource-collection> <auth-constraint> <role-name>basicUser</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-constraint>BASIC</auth-constraint> <realm-name>file</realm-name> </login-config> </web-app>
The role of basicUser has been defined for this application, but there is no group of basicUser defined for the Application Server. To map the role that is defined for the application (basicUser) to a group that is defined on the Application Server (user), add a <security-role-mapping> element to the runtime deployment descriptor, sun-web.xml, as shown below (modifications from the original file are in bold). The resulting runtime deployment descriptor is located in tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth/web/WEB-INF/sun-web.xml.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Servlet 2.5//EN" "http://www.sun.com/software/appserver/dtds/sun-web-app_2_5-0.dtd"> <sun-web-app error-url=""> <context-root>/helloservice</context-root> <class-loader delegate="true"/> <security-role-mapping> <role-name>basicUser</role-name> <group-name>user</group-name> </security-role-mapping> </sun-web-app>
To build, package, and deploy the jaxws/helloservice-basicauth example using NetBeans IDE, follow these steps, or the steps described in Building, Packaging, and Deploying the Service.
If you have not already done so, set up your system for running the tutorial examples by following the instructions in Building the Examples.
If you haven’t already done so, set up an authorized user on the Application Server, assigned to the group user, as described in Managing Users and Groups on the Application Server.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jaxws/.
Select the helloservice-basicauth folder.
Check the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the helloservice-basicauth project and select Clean and Build.
In the Projects tab, right-click the helloservice-basicauth project and select Undeploy and Deploy.
This step builds and packages the application into helloservice-basicauth.war, located in tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth/dist, and deploys this war file to your Application Server instance.
To build, package, and deploy the jaxws/helloservice-basicauth example using the Ant tool, follow these steps, or the steps described in Building, Packaging, and Deploying the Service.
If you have not already done so, set up your system for running the tutorial examples by following the instructions in Building the Examples.
If you haven’t already done so, set up an authorized user on the Application Server, assigned to the group user, as described in Managing Users and Groups on the Application Server.
From a terminal window or command prompt, go to the tut-install/javaeetutorial5/examples/jaxws/helloservice-basicauth/ directory.
Build, package, and deploy the JAX-WS service by entering the following at the terminal window or command prompt in the helloservice-basicauth/ directory:
ant all |
You can test the service by selecting it in the Admin Console and choosing Test. For more information on how to do this, read Testing the Service without a Client.
To build and run the client application, simpleclient-basicauth, using NetBeans IDE, follow these steps. The helloservice-basicauth service must be deployed onto the Application Server before compiling the client files. For information on deploying the service, read Building and Deploying helloservice with Basic Authentication Using NetBeans IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jaxws/.
Select the simpleclient-basicauth folder.
Check the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the simpleclient-basicauth project and select Clean and Build.
In the Projects tab, right-click the simpleclient-basicauth project and select Run.
You will be prompted for your user name and password.
Enter the user name and password of a user that has been entered into the database of users for the file realm and has been assigned to the group of user.
If the username and password you enter are authorized, you will see the output of the application client in the Output pane.
The client displays the following output:
[echo] running application client container. [exec] Retrieving the port from the following service: helloservice.basicauth.endpoint.HelloService@c8769b [exec] Invoking the sayHello operation on the port. [exec] Hello, No Name. |
To build and run the client application, simpleclient-basicauth, using the Ant tool, follow these steps. The secured service must be deployed onto the Application Server before you can successfully compile the client application. For more information on deploying the service, read Building and Deploying helloservice with Basic Authentication Using Ant.
Build the client by changing to the directory tut-install/examples/jaxws/simpleclient-basicauth/ and entering the following at the terminal window or command prompt:
ant |
This command calls the default target, which builds and packages the application into a JAR file, simpleclient-basicauth.jar, located in the /dist directory.
Run the client by entering the following at the terminal window or command prompt:
ant run |
A Login for User dialog displays.
Enter a user name and password that correspond to a user set up on the Application Server with a group of user. Click OK.
The client displays the following output:
[echo] running application client container. [exec] Retrieving the port from the following service: helloservice.basicauth.endpoint.HelloService@c8769b [exec] Invoking the sayHello operation on the port. [exec] Hello, No Name. |
This chapter provides an introduction to the Java Message Service (JMS) API, a Java API that allows applications to create, send, receive, and read messages using reliable, asynchronous, loosely coupled communication. It covers the following topics:
This overview of the JMS API answers the following questions.
Messaging is a method of communication between software components or applications. A messaging system is a peer-to-peer facility: A messaging client can send messages to, and receive messages from, any other client. Each client connects to a messaging agent that provides facilities for creating, sending, receiving, and reading messages.
Messaging enables distributed communication that is loosely coupled. A component sends a message to a destination, and the recipient can retrieve the message from the destination. However, the sender and the receiver do not have to be available at the same time in order to communicate. In fact, the sender does not need to know anything about the receiver; nor does the receiver need to know anything about the sender. The sender and the receiver need to know only which message format and which destination to use. In this respect, messaging differs from tightly coupled technologies, such as Remote Method Invocation (RMI), which require an application to know a remote application’s methods.
Messaging also differs from electronic mail (email), which is a method of communication between people or between software applications and people. Messaging is used for communication between software applications or software components.
The Java Message Service is a Java API that allows applications to create, send, receive, and read messages. Designed by Sun and several partner companies, the JMS API defines a common set of interfaces and associated semantics that allow programs written in the Java programming language to communicate with other messaging implementations.
The JMS API minimizes the set of concepts a programmer must learn in order to use messaging products but provides enough features to support sophisticated messaging applications. It also strives to maximize the portability of JMS applications across JMS providers in the same messaging domain.
The JMS API enables communication that is not only loosely coupled but also
Asynchronous: A JMS provider can deliver messages to a client as they arrive; a client does not have to request messages in order to receive them.
Reliable: The JMS API can ensure that a message is delivered once and only once. Lower levels of reliability are available for applications that can afford to miss messages or to receive duplicate messages.
The JMS specification was first published in August 1998. The latest version is Version 1.1, which was released in April 2002. You can download a copy of the specification from the JMS web site: http://java.sun.com/products/jms/.
An enterprise application provider is likely to choose a messaging API over a tightly coupled API, such as remote procedure call (RPC), under the following circumstances.
The provider wants the components not to depend on information about other components’ interfaces, so that components can be easily replaced.
The provider wants the application to run whether or not all components are up and running simultaneously.
The application business model allows a component to send information to another and to continue to operate without receiving an immediate response.
For example, components of an enterprise application for an automobile manufacturer can use the JMS API in situations like these:
The inventory component can send a message to the factory component when the inventory level for a product goes below a certain level so that the factory can make more cars.
The factory component can send a message to the parts components so that the factory can assemble the parts it needs.
The parts components in turn can send messages to their own inventory and order components to update their inventories and to order new parts from suppliers.
Both the factory and the parts components can send messages to the accounting component to update their budget numbers.
The business can publish updated catalog items to its sales force.
Using messaging for these tasks allows the various components to interact with one another efficiently, without tying up network or other resources. Figure 31–1 illustrates how this simple example might work.
Manufacturing is only one example of how an enterprise can use the JMS API. Retail applications, financial services applications, health services applications, and many others can make use of messaging.
When the JMS API was introduced in 1998, its most important purpose was to allow Java applications to access existing messaging-oriented middleware (MOM) systems, such as MQSeries from IBM. Since that time, many vendors have adopted and implemented the JMS API, so a JMS product can now provide a complete messaging capability for an enterprise.
Beginning with the 1.3 release of the Java EE platform, the JMS API has been an integral part of the platform, and application developers can use messaging with Java EE components.
The JMS API in the Java EE platform has the following features.
Application clients, Enterprise JavaBeans (EJB) components, and web components can send or synchronously receive a JMS message. Application clients can in addition receive JMS messages asynchronously. (Applets, however, are not required to support the JMS API.)
Message-driven beans, which are a kind of enterprise bean, enable the asynchronous consumption of messages. A JMS provider can optionally implement concurrent processing of messages by message-driven beans.
Message send and receive operations can participate in distributed transactions, which allow JMS operations and database accesses to take place within a single transaction.
The JMS API enhances the Java EE platform by simplifying enterprise development, allowing loosely coupled, reliable, asynchronous interactions among Java EE components and legacy systems capable of messaging. A developer can easily add new behavior to a Java EE application that has existing business events by adding a new message-driven bean to operate on specific business events. The Java EE platform, moreover, enhances the JMS API by providing support for distributed transactions and allowing for the concurrent consumption of messages. For more information, see the Enterprise JavaBeans specification, v3.0.
The JMS provider can be integrated with the application server using the Java EE Connector architecture. You access the JMS provider through a resource adapter. This capability allows vendors to create JMS providers that can be plugged in to multiple application servers, and it allows application servers to support multiple JMS providers. For more information, see the Java EE Connector architecture specification, v1.5.
This section introduces the most basic JMS API concepts, the ones you must know to get started writing simple JMS client applications:
The next section introduces the JMS API programming model. Later sections cover more advanced concepts, including the ones you need to write Java EE applications that use message-driven beans.
A JMS application is composed of the following parts.
A JMS provider is a messaging system that implements the JMS interfaces and provides administrative and control features. An implementation of the Java EE platform includes a JMS provider.
JMS clients are the programs or components, written in the Java programming language, that produce and consume messages. Any Java EE application component can act as a JMS client.
Messages are the objects that communicate information between JMS clients.
Administered objects are preconfigured JMS objects created by an administrator for the use of clients. The two kinds of JMS administered objects are destinations and connection factories, which are described in JMS Administered Objects.
Figure 31–2 illustrates the way these parts interact. Administrative tools allow you to bind destinations and connection factories into a JNDI namespace. A JMS client can then use resource injection to access the administered objects in the namespace and then establish a logical connection to the same objects through the JMS provider.
Before the JMS API existed, most messaging products supported either the point-to-point or the publish/subscribe approach to messaging. The JMS specification provides a separate domain for each approach and defines compliance for each domain. A stand-alone JMS provider can implement one or both domains. A Java EE provider must implement both domains.
In fact, most implementations of the JMS API support both the point-to-point and the publish/subscribe domains, and some JMS clients combine the use of both domains in a single application. In this way, the JMS API has extended the power and flexibility of messaging products.
The JMS 1.1 specification goes one step further: It provides common interfaces that enable you to use the JMS API in a way that is not specific to either domain. The following subsections describe the two messaging domains and then describe the use of the common interfaces.
A point-to-point (PTP) product or application is built on the concept of message queues, senders, and receivers. Each message is addressed to a specific queue, and receiving clients extract messages from the queues established to hold their messages. Queues retain all messages sent to them until the messages are consumed or until the messages expire.
PTP messaging has the following characteristics and is illustrated in Figure 31–3.
Each message has only one consumer.
A sender and a receiver of a message have no timing dependencies. The receiver can fetch the message whether or not it was running when the client sent the message.
The receiver acknowledges the successful processing of a message.
Use PTP messaging when every message you send must be processed successfully by one consumer.
In a publish/subscribe (pub/sub) product or application, clients address messages to a topic, which functions somewhat like a bulletin board. Publishers and subscribers are generally anonymous and can dynamically publish or subscribe to the content hierarchy. The system takes care of distributing the messages arriving from a topic’s multiple publishers to its multiple subscribers. Topics retain messages only as long as it takes to distribute them to current subscribers.
Pub/sub messaging has the following characteristics.
Each message can have multiple consumers.
Publishers and subscribers have a timing dependency. A client that subscribes to a topic can consume only messages published after the client has created a subscription, and the subscriber must continue to be active in order for it to consume messages.
The JMS API relaxes this timing dependency to some extent by allowing subscribers to create durable subscriptions, which receive messages sent while the subscribers are not active. Durable subscriptions provide the flexibility and reliability of queues but still allow clients to send messages to many recipients. For more information about durable subscriptions, see Creating Durable Subscriptions.
Use pub/sub messaging when each message can be processed by zero, one, or many consumers. Figure 31–4 illustrates pub/sub messaging.
Version 1.1 of the JMS API allows you to use the same code to send and receive messages under either the PTP or the pub/sub domain. The destinations that you use remain domain-specific, and the behavior of the application will depend in part on whether you are using a queue or a topic. However, the code itself can be common to both domains, making your applications flexible and reusable. This tutorial describes and illustrates these common interfaces.
Messaging products are inherently asynchronous: There is no fundamental timing dependency between the production and the consumption of a message. However, the JMS specification uses this term in a more precise sense. Messages can be consumed in either of two ways:
Synchronously: A subscriber or a receiver explicitly fetches the message from the destination by calling the receive method. The receive method can block until a message arrives or can time out if a message does not arrive within a specified time limit.
Asynchronously: A client can register a message listener with a consumer. A message listener is similar to an event listener. Whenever a message arrives at the destination, the JMS provider delivers the message by calling the listener’s onMessage method, which acts on the contents of the message.
The basic building blocks of a JMS application consist of
Administered objects: connection factories and destinations
Connections
Sessions
Message producers
Message consumers
Messages
Figure 31–5 shows how all these objects fit together in a JMS client application.
This section describes all these objects briefly and provides sample commands and code snippets that show how to create and use the objects. The last subsection briefly describes JMS API exception handling.
Examples that show how to combine all these objects in applications appear in later sections. For more details, see the JMS API documentation, which is part of the Java EE API documentation.
Two parts of a JMS application, destinations and connection factories, are best maintained administratively rather than programmatically. The technology underlying these objects is likely to be very different from one implementation of the JMS API to another. Therefore, the management of these objects belongs with other administrative tasks that vary from provider to provider.
JMS clients access these objects through interfaces that are portable, so a client application can run with little or no change on more than one implementation of the JMS API. Ordinarily, an administrator configures administered objects in a JNDI namespace, and JMS clients then access them by using resource injection.
With Sun Java System Application Server Platform Edition 9, you use the asadmin command or the Admin Console to create JMS administered objects in the form of resources.
A connection factory is the object a client uses to create a connection to a provider. A connection factory encapsulates a set of connection configuration parameters that has been defined by an administrator. Each connection factory is an instance of the ConnectionFactory, QueueConnectionFactory, or TopicConnectionFactory interface. To learn how to create connection factories, see Creating JMS Administered Objects for the Synchronous Receive Example.
At the beginning of a JMS client program, you usually inject a connection factory resource into a ConnectionFactory object. For example, the following code fragment specifies a resource whose JNDI name is jms/ConnectionFactory and assigns it to a ConnectionFactory object:
@Resource(mappedName="jms/ConnectionFactory") private static ConnectionFactory connectionFactory;
In a Java EE application, JMS administered objects are normally placed in the jms naming subcontext.
The mappedName element of the @Resource annotation is specific to the Application Server.
A destination is the object a client uses to specify the target of messages it produces and the source of messages it consumes. In the PTP messaging domain, destinations are called queues. In the pub/sub messaging domain, destinations are called topics. A JMS application can use multiple queues or topics (or both). To learn how to create destination resources, see Creating JMS Administered Objects for the Synchronous Receive Example.
To create a destination using the Application Server, you create a JMS destination resource that specifies a JNDI name for the destination.
In the Application Server implementation of JMS, each destination resource refers to a physical destination. You can create a physical destination explicitly, but if you do not, the Application Server creates it when it is needed and deletes it when you delete the destination resource.
In addition to injecting a connection factory resource into a client program, you usually inject a destination resource. Unlike connection factories, destinations are specific to one domain or the other. To create an application that allows you to use the same code for both topics and queues, you assign the destination to a Destination object.
The following code specifies two resources, a queue and a topic. The resource names are mapped to destinations created in the JNDI namespace.
@Resource(mappedName="jms/Queue") private static Queue queue; @Resource(mappedName="jms/Topic") private static Topic topic;
With the common interfaces, you can mix or match connection factories and destinations. That is, in addition to using the ConnectionFactory interface, you can inject a QueueConnectionFactory resource and use it with a Topic, and you can inject a TopicConnectionFactory resource and use it with a Queue. The behavior of the application will depend on the kind of destination you use and not on the kind of connection factory you use.
A connection encapsulates a virtual connection with a JMS provider. A connection could represent an open TCP/IP socket between a client and a provider service daemon. You use a connection to create one or more sessions.
Connections implement the Connection interface. When you have a ConnectionFactory object, you can use it to create a Connection:
Connection connection = connectionFactory.createConnection();
Before an application completes, you must close any connections that you have created. Failure to close a connection can cause resources not to be released by the JMS provider. Closing a connection also closes its sessions and their message producers and message consumers.
connection.close();
Before your application can consume messages, you must call the connection’s start method; for details, see JMS Message Consumers. If you want to stop message delivery temporarily without closing the connection, you call the stop method.
A session is a single-threaded context for producing and consuming messages. You use sessions to create the following:
Message producers
Message consumers
Messages
Queue browsers
Temporary queues and topics (see Creating Temporary Destinations)
Sessions serialize the execution of message listeners; for details, see JMS Message Listeners.
A session provides a transactional context with which to group a set of sends and receives into an atomic unit of work. For details, see Using JMS API Local Transactions.
Sessions implement the Session interface. After you create a Connection object, you use it to create a Session:
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
The first argument means that the session is not transacted; the second means that the session automatically acknowledges messages when they have been received successfully. (For more information, see Controlling Message Acknowledgment.)
To create a transacted session, use the following code:
Session session = connection.createSession(true, 0);
Here, the first argument means that the session is transacted; the second indicates that message acknowledgment is not specified for transacted sessions. For more information on transactions, see Using JMS API Local Transactions. For information about the way JMS transactions work in Java EE applications, see Using the JMS API in a Java EE Application.
A message producer is an object that is created by a session and used for sending messages to a destination. It implements the MessageProducer interface.
You use a Session to create a MessageProducer for a destination. The following examples show that you can create a producer for a Destination object, a Queue object, or a Topic object:
MessageProducer producer = session.createProducer(dest); MessageProducer producer = session.createProducer(queue); MessageProducer producer = session.createProducer(topic);
You can create an unidentified producer by specifying null as the argument to createProducer. With an unidentified producer, you do not specify a destination until you send a message.
After you have created a message producer, you can use it to send messages by using the send method:
producer.send(message);
You must first create the messages; see JMS Messages.
If you created an unidentified producer, use an overloaded send method that specifies the destination as the first parameter. For example:
MessageProducer anon_prod = session.createProducer(null); anon_prod.send(dest, message);
A message consumer is an object that is created by a session and used for receiving messages sent to a destination. It implements the MessageConsumer interface.
A message consumer allows a JMS client to register interest in a destination with a JMS provider. The JMS provider manages the delivery of messages from a destination to the registered consumers of the destination.
For example, you could use a Session to create a MessageConsumer for a Destination object, a Queue object, or a Topic object:
MessageConsumer consumer = session.createConsumer(dest); MessageConsumer consumer = session.createConsumer(queue); MessageConsumer consumer = session.createConsumer(topic);
You use the Session.createDurableSubscriber method to create a durable topic subscriber. This method is valid only if you are using a topic. For details, see Creating Durable Subscriptions.
After you have created a message consumer, it becomes active, and you can use it to receive messages. You can use the close method for a MessageConsumer to make the message consumer inactive. Message delivery does not begin until you start the connection you created by calling its start method. (Remember always to call the start method; forgetting to start the connection is one of the most common JMS programming errors.)
You use the receive method to consume a message synchronously. You can use this method at any time after you call the start method:
connection.start(); Message m = consumer.receive(); connection.start(); Message m = consumer.receive(1000); // time out after a second
To consume a message asynchronously, you use a message listener, described in the next section.
A message listener is an object that acts as an asynchronous event handler for messages. This object implements the MessageListener interface, which contains one method, onMessage. In the onMessage method, you define the actions to be taken when a message arrives.
You register the message listener with a specific MessageConsumer by using the setMessageListener method. For example, if you define a class named Listener that implements the MessageListener interface, you can register the message listener as follows:
Listener myListener = new Listener(); consumer.setMessageListener(myListener);
After you register the message listener, you call the start method on the Connection to begin message delivery. (If you call start before you register the message listener, you are likely to miss messages.)
When message delivery begins, the JMS provider automatically calls the message listener’s onMessage method whenever a message is delivered. The onMessage method takes one argument of type Message, which your implementation of the method can cast to any of the other message types (see Message Bodies).
A message listener is not specific to a particular destination type. The same listener can obtain messages from either a queue or a topic, depending on the type of destination for which the message consumer was created. A message listener does, however, usually expect a specific message type and format.
Your onMessage method should handle all exceptions. It must not throw checked exceptions, and throwing a RuntimeException is considered a programming error.
The session used to create the message consumer serializes the execution of all message listeners registered with the session. At any time, only one of the session’s message listeners is running.
In the Java EE platform, a message-driven bean is a special kind of message listener. For details, see Using Message-Driven Beans to Receive Messages Asynchronously.
If your messaging application needs to filter the messages it receives, you can use a JMS API message selector, which allows a message consumer to specify the messages it is interested in. Message selectors assign the work of filtering messages to the JMS provider rather than to the application. For an example of an application that uses a message selector, see A Java EE Application That Uses the JMS API with a Session Bean.
A message selector is a String that contains an expression. The syntax of the expression is based on a subset of the SQL92 conditional expression syntax. The message selector in the example selects any message that has a NewsType property that is set to the value 'Sports' or 'Opinion':
NewsType = ’Sports’ OR NewsType = ’Opinion’
The createConsumer, createDurableSubscriber methods allow you to specify a message selector as an argument when you create a message consumer.
The message consumer then receives only messages whose headers and properties match the selector. (See Message Headers, and Message Properties.) A message selector cannot select messages on the basis of the content of the message body.
The ultimate purpose of a JMS application is to produce and to consume messages that can then be used by other software applications. JMS messages have a basic format that is simple but highly flexible, allowing you to create messages that match formats used by non-JMS applications on heterogeneous platforms.
A JMS message has three parts: a header, properties, and a body. Only the header is required. The following sections describe these parts:
For complete documentation of message headers, properties, and bodies, see the documentation of the Message interface in the API documentation.
A JMS message header contains a number of predefined fields that contain values that both clients and providers use to identify and to route messages. Table 31–1 lists the JMS message header fields and indicates how their values are set. For example, every message has a unique identifier, which is represented in the header field JMSMessageID. The value of another header field, JMSDestination, represents the queue or the topic to which the message is sent. Other fields include a timestamp and a priority level.
Each header field has associated setter and getter methods, which are documented in the description of the Message interface. Some header fields are intended to be set by a client, but many are set automatically by the send or the publish method, which overrides any client-set values.
Table 31–1 How JMS Message Header Field Values Are Set
Header Field |
Set By |
---|---|
send or publish method |
|
send or publish method |
|
send or publish method |
|
send or publish method |
|
send or publish method |
|
send or publish method |
|
Client |
|
Client |
|
Client |
|
JMS provider |
You can create and set properties for messages if you need values in addition to those provided by the header fields. You can use properties to provide compatibility with other messaging systems, or you can use them to create message selectors (see JMS Message Selectors). For an example of setting a property to be used as a message selector, see A Java EE Application That Uses the JMS API with a Session Bean.
The JMS API provides some predefined property names that a provider can support. The use either of these predefined properties or of user-defined properties is optional.
The JMS API defines five message body formats, also called message types, which allow you to send and to receive data in many different forms and provide compatibility with existing messaging formats. Table 31–2 describes these message types.
Table 31–2 JMS Message Types
Message Type |
Body Contains |
---|---|
A java.lang.String object (for example, the contents of an XML file). |
|
A set of name-value pairs, with names as String objects and values as primitive types in the Java programming language. The entries can be accessed sequentially by enumerator or randomly by name. The order of the entries is undefined. |
|
A stream of uninterpreted bytes. This message type is for literally encoding a body to match an existing message format. |
|
A stream of primitive values in the Java programming language, filled and read sequentially. |
|
A Serializable object in the Java programming language. |
|
Nothing. Composed of header fields and properties only. This message type is useful when a message body is not required. |
The JMS API provides methods for creating messages of each type and for filling in their contents. For example, to create and send a TextMessage, you might use the following statements:
TextMessage message = session.createTextMessage(); message.setText(msg_text); // msg_text is a String producer.send(message);
At the consuming end, a message arrives as a generic Message object and must be cast to the appropriate message type. You can use one or more getter methods to extract the message contents. The following code fragment uses the getText method:
Message m = consumer.receive(); if (m instanceof TextMessage) { TextMessage message = (TextMessage) m; System.out.println("Reading message: " + message.getText()); } else { // Handle error }
You can create a QueueBrowser object to inspect the messages in a queue. Messages sent to a queue remain in the queue until the message consumer for that queue consumes them. Therefore, the JMS API provides an object that allows you to browse the messages in the queue and display the header values for each message. To create a QueueBrowser object, use the Session.createBrowser method. For example:
QueueBrowser browser = session.createBrowser(queue);
See A Simple Example of Browsing Messages in a Queue for an example of the use of a QueueBrowser object.
The createBrowser method allows you to specify a message selector as a second argument when you create a QueueBrowser. For information on message selectors, see JMS Message Selectors.
The JMS API provides no mechanism for browsing a topic. Messages usually disappear from a topic as soon as they appear: if there are no message consumers to consume them, the JMS provider removes them. Although durable subscriptions allow messages to remain on a topic while the message consumer is not active, no facility exists for examining them.
The root class for exceptions thrown by JMS API methods is JMSException. Catching JMSException provides a generic way of handling all exceptions related to the JMS API.
The JMSException class includes the following subclasses, which are described in the API documentation:
IllegalStateException
InvalidClientIDException
InvalidDestinationException
InvalidSelectorException
JMSSecurityException
MessageEOFException
MessageFormatException
MessageNotReadableException
MessageNotWriteableException
ResourceAllocationException
TransactionInProgressException
TransactionRolledBackException
All the examples in the tutorial catch and handle JMSException when it is appropriate to do so.
This section shows how to create, package, and run simple JMS client programs packaged as stand-alone application clients. These clients access a Java EE server. The clients demonstrate the basic tasks that a JMS application must perform:
Creating a connection and a session
Creating message producers and consumers
Sending and receiving messages
In a Java EE application, some of these tasks are performed, in whole or in part, by the container. If you learn about these tasks, you will have a good basis for understanding how a JMS application works on the Java EE platform.
This section covers the following topics:
Each example uses two programs: one that sends messages and one that receives them. You can run the programs in NetBeans IDE or in two terminal windows.
When you write a JMS application to run in a Java EE application, you use many of the same methods in much the same sequence as you do for a stand-alone application client. However, there are some significant differences. Using the JMS API in a Java EE Application describes these differences, and Chapter 32, Java EE Examples Using the JMS API provides examples that illustrate them.
The examples for this section are in the following directory:
tut-install/javaeetutorial5/examples/jms/simple/
The examples are in the following four subdirectories:
producer synchconsumer asynchconsumer messagebrowser
This section describes the sending and receiving programs in an example that uses the receive method to consume messages synchronously. This section then explains how to compile, package, and run the programs using the Application Server.
The following sections describe the steps in creating and running the example:
Writing the Client Programs for the Synchronous Receive Example
Creating JMS Administered Objects for the Synchronous Receive Example
Compiling and Packaging the Clients for the Synchronous Receive Example
The sending program, producer/src/java/Producer.java, performs the following steps:
Injects resources for a connection factory, queue, and topic:
@Resource(mappedName="jms/ConnectionFactory") private static ConnectionFactory connectionFactory; @Resource(mappedName="jms/Queue")private static Queue queue; @Resource(mappedName="jms/Topic")private static Topic topic;
Retrieves and verifies command-line arguments that specify the destination type and the number of arguments:
final int NUM_MSGS; String destType = args[0]; System.out.println("Destination type is " + destType); if ( ! ( destType.equals("queue") || destType.equals("topic") ) ) { System.err.println("Argument must be \”queue\” or " + "\”topic\”"); System.exit(1); } if (args.length == 2){ NUM_MSGS = (new Integer(args[1])).intValue(); } else { NUM_MSGS = 1; }
Assigns either the queue or topic to a destination object, based on the specified destination type:
Destination dest = null; try { if (destType.equals("queue")) { dest = (Destination) queue; } else { dest = (Destination) topic; } } catch (Exception e) { System.err.println("Error setting destination: " + e.toString()); e.printStackTrace(); System.exit(1); }
Creates a Connection and a Session:
Connection connection = connectionFactory.createConnection(); Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Creates a MessageProducer and a TextMessage:
MessageProducer producer = session.createProducer(dest); TextMessage message = session.createTextMessage();
Sends one or more messages to the destination:
for (int i = 0; i < NUM_MSGS; i++) { message.setText("This is message " + (i + 1)); System.out.println("Sending message: " + message.getText()); producer.send(message); }
Sends an empty control message to indicate the end of the message stream:
producer.send(session.createMessage());
Sending an empty message of no specified type is a convenient way to indicate to the consumer that the final message has arrived.
Closes the connection in a finally block, automatically closing the session and MessageProducer:
} finally { if (connection != null) { try { connection.close(); } catch (JMSException e) { } } }
The receiving program, synchconsumer/src/java/SynchConsumer.java, performs the following steps:
Injects resources for a connection factory, queue, and topic.
Assigns either the queue or topic to a destination object, based on the specified destination type.
Creates a Connection and a Session.
Creates a MessageConsumer:
consumer = session.createConsumer(dest);
Starts the connection, causing message delivery to begin:
connection.start();
Receives the messages sent to the destination until the end-of-message-stream control message is received:
while (true) { Message m = consumer.receive(1); if (m != null) { if (m instanceof TextMessage) { message = (TextMessage) m; System.out.println("Reading message: " + message.getText()); } else { break; } } }
Because the control message is not a TextMessage, the receiving program terminates the while loop and stops receiving messages after the control message arrives.
Closes the connection in a finally block, automatically closing the session and MessageConsumer.
The receive method can be used in several ways to perform a synchronous receive. If you specify no arguments or an argument of 0, the method blocks indefinitely until a message arrives:
Message m = consumer.receive(); Message m = consumer.receive(0);
For a simple client program, this may not matter. But if you do not want your program to consume system resources unnecessarily, use a timed synchronous receive. Do one of the following:
Call the receive method with a timeout argument greater than 0:
Message m = consumer.receive(1); // 1 millisecond
Call the receiveNoWait method, which receives a message only if one is available:
Message m = consumer.receiveNoWait();
The SynchConsumer program uses an indefinite while loop to receive messages, calling receive with a timeout argument. Calling receiveNoWait would have the same effect.
When you use the Application Server, your JMS provider is the Application Server. Start the server as described in Starting and Stopping the Application Server.
Creating the JMS administered objects for this section involves the following:
Creating a connection factory
Creating two destination resources
If you built and ran the SimpleMessage example in Chapter 23, A Message-Driven Bean Example and did not delete the resources afterward, you need to create only the topic resource.
You can create these objects using the Ant tool. To create all the resources, do the following:
In a terminal window, go to the producer directory:
cd producer |
To create all the resources, type the following command:
ant create-resources |
To create only the topic resource, type the following command:
ant create-topic |
These Ant targets use the asadmin create-jms-resource command to create the connection factory and the destination resources.
To verify that the resources have been created, use the following command:
asadmin list-jms-resources |
The output looks like this:
jms/Queue jms/Topic jms/ConnectionFactory Command list-jms-resources executed successfully. |
The simplest way to run these examples using the Application Server is to package each one in an application client JAR file. The application client JAR file requires a manifest file, located in the src/conf directory for each example, along with the .class file.
The build.xml file for each example contains Ant targets that compile and package the example. The targets place the .class file for the example in the build/jar directory. Then the targets use the jar command to package the class file and the manifest file in an application client JAR file.
To compile and package the Producer and SynchConsumer examples using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.
Select the producer folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.
Select the synchconsumer folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the Producer and SynchConsumer examples using Ant, follow these steps:
In a terminal window, go to the producer directory:
cd producer |
Type the following command:
ant |
In a terminal window, go to the synchconsumer directory:
cd ../synchconsumer |
Type the following command:
ant |
The targets place the application client JAR file in the dist directory for each example.
To run the sample programs using NetBeans IDE, follow these steps.
Run the Producer example:
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue 3 |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
The messages are now in the queue, waiting to be received.
Now run the SynchConsumer example:
Right-click the synchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 |
Now try running the programs in the opposite order. Right-click the synchconsumer project and choose Run.
The Output pane displays the destination type and then appears to hang, waiting for messages.
Right-click the producer project and choose Run.
The Output pane shows the output of both programs, in two different tabs.
Now run the Producer example using a topic instead of a queue.
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
topic 3 |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is topic Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
Now run the SynchConsumer example using the topic.
Right-click the synchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
topic |
Click OK.
Right-click the project and choose Run.
The result, however, is different. Because you are using a topic, messages that were sent before you started the consumer cannot be received. (See Publish/Subscribe Messaging Domain, for details.) Instead of receiving the messages, the program appears to hang.
Run the Producer example again. Right-click the producer project and choose Run.
Now the SynchConsumer example receives the messages:
Destination type is topic Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 |
You can also run the sample programs using the appclient command. Each of the programs takes one or more command-line arguments: a destination type and, for Producer, a number of messages.
To run the clients using the appclient command, follow these steps:
In a terminal window, go to the producer/dist directory:
cd ../producer/dist |
Run the Producer program, sending three messages to the queue:
appclient -client producer.jar queue 3 |
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
The messages are now in the queue, waiting to be received.
In the same window, go to the synchconsumer/dist directory:
cd ../../synchconsumer/dist |
Run the SynchConsumer program, specifying the queue:
appclient -client synchconsumer.jar queue |
The output of the program looks like this:
Destination type is queue Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 |
Now try running the programs in the opposite order. Run the SynchConsumer program. It displays the destination type and then appears to hang, waiting for messages.
appclient -client synchconsumer.jar queue |
In a different terminal window, run the Producer program.
cd tut-install/javaeetutorial5/examples/jms/simple/producer/dist appclient -client producer.jar queue 3 |
When the messages have been sent, the SynchConsumer program receives them and exits.
Now run the Producer program using a topic instead of a queue:
appclient -client producer.jar topic 3 |
The output of the program looks like this:
Destination type is topic Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
Now run the SynchConsumer program using the topic:
appclient -client synchconsumer.jar topic |
The result, however, is different. Because you are using a topic, messages that were sent before you started the consumer cannot be received. (See Publish/Subscribe Messaging Domain, for details.) Instead of receiving the messages, the program appears to hang.
Run the Producer program again. Now the SynchConsumer program receives the messages:
Destination type is topic Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 |
Because the examples use the common interfaces, you can run them using either a queue or a topic.
This section describes the receiving programs in an example that uses a message listener to consume messages asynchronously. This section then explains how to compile and run the programs using the Application Server.
The following sections describe the steps in creating and running the example:
The sending program is producer/src/java/Producer.java, the same program used in the example in A Simple Example of Synchronous Message Receives.
An asynchronous consumer normally runs indefinitely. This one runs until the user types the letter q or Q to stop the program.
The receiving program, asynchconsumer/src/java/AsynchConsumer.java, performs the following steps:
Injects resources for a connection factory, queue, and topic.
Assigns either the queue or topic to a destination object, based on the specified destination type.
Creates a Connection and a Session.
Creates a MessageConsumer.
Creates an instance of the TextListener class and registers it as the message listener for the MessageConsumer:
listener = new TextListener();consumer.setMessageListener(listener);
Starts the connection, causing message delivery to begin.
Listens for the messages published to the destination, stopping when the user types the character q or Q:
System.out.println("To end program, type Q or q, " + "then <return>"); inputStreamReader = new InputStreamReader(System.in); while (!((answer == ’q’) || (answer == ’Q’))) { try { answer = (char) inputStreamReader.read(); } catch (IOException e) { System.out.println("I/O exception: " + e.toString()); } }
Closes the connection, which automatically closes the session and MessageConsumer.
The message listener, asynchconsumer/src/java/TextListener.java, follows these steps:
When a message arrives, the onMessage method is called automatically.
The onMessage method converts the incoming message to a TextMessage and displays its content. If the message is not a text message, it reports this fact:
public void onMessage(Message message) { TextMessage msg = null; try { if (message instanceof TextMessage) { msg = (TextMessage) message; System.out.println("Reading message: " + msg.getText()); } else { System.out.println("Message is not a " + "TextMessage"); } } catch (JMSException e) { System.out.println("JMSException in onMessage(): " + e.toString()); } catch (Throwable t) { System.out.println("Exception in onMessage():" + t.getMessage()); } }
You will use the connection factory and destinations you created in Creating JMS Administered Objects for the Synchronous Receive Example.
To compile and package the AsynchConsumer example using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.
Select the asynchconsumer folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the AsynchConsumer example using Ant, follow these steps:
In a terminal window, go to the asynchconsumer directory:
cd ../../asynchconsumer |
Type the following command:
ant |
The targets package both the main class and the message listener class in the JAR file and place the file in the dist directory for the example.
To run the programs using NetBeans IDE, follow these steps.
Run the AsynchConsumer example:
Right-click the asynchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
topic |
Click OK.
Right-click the project and choose Run.
The program displays the following lines and appears to hang:
Destination type is topic To end program, type Q or q, then <return> |
Now run the Producer example:
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
topic 3 |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is topic Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
In the other window, the AsynchConsumer program displays the following:
Destination type is topic To end program, type Q or q, then <return> Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 Message is not a TextMessage |
The last line appears because the program has received the non-text control message sent by the Producer program.
Type Q or q in the Output window and press Return to stop the program.
Now run the programs using a queue. In this case, as with the synchronous example, you can run the Producer program first, because there is no timing dependency between the sender and receiver.
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue 3 |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
Run the AsynchConsumer program.
Right-click the asynchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue To end program, type Q or q, then <return> Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 Message is not a TextMessage |
Type Q or q in the Output window and press Return to stop the program.
To run the clients using the appclient command, follow these steps:
Run the AsynchConsumer program, specifying the topic destination type.
cd dist appclient -client asynchconsumer.jar topic |
The program displays the following lines and appears to hang:
Destination type is topic To end program, type Q or q, then <return> |
In the terminal window where you ran the Producer program previously, run the program again, sending three messages. The command looks like this:
appclient -client producer.jar topic 3 |
The output of the program looks like this:
Destination type is topic Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
In the other window, the AsynchConsumer program displays the following:
Destination type is topic To end program, type Q or q, then <return> Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 Message is not a TextMessage |
The last line appears because the program has received the non-text control message sent by the Producer program.
Type Q or q and press Return to stop the program.
Now run the programs using a queue. In this case, as with the synchronous example, you can run the Producer program first, because there is no timing dependency between the sender and receiver:
appclient -client producer.jar queue 3 |
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
Run the AsynchConsumer program:
appclient -client asynchconsumer.jar queue |
The output of the program looks like this:
Destination type is queue To end program, type Q or q, then <return> Reading message: This is message 1 Reading message: This is message 2 Reading message: This is message 3 Message is not a TextMessage |
Type Q or q to stop the program.
This section describes an example that creates a QueueBrowser object to examine messages on a queue, as described in JMS Queue Browsers. This section then explains how to compile, package, and run the example using the Application Server.
The following sections describe the steps in creating and running the example:
To create a QueueBrowser for a queue, you call the Session.createBrowser method with the queue as the argument. You obtain the messages in the queue as an Enumeration object. You can then iterate through the Enumeration object and display the contents of each message.
The messagebrowser/src/java/MessageBrowser.java program performs the following steps:
Injects resources for a connection factory and a queue.
Creates a Connection and a Session.
Creates a QueueBrowser:
QueueBrowser browser = session.createBrowser(queue);
Retrieves the Enumeration that contains the messages:
Enumeration msgs = browser.getEnumeration();
Verifies that the Enumeration contains messages, then displays the contents of the messages:
if ( !msgs.hasMoreElements() ) { System.out.println("No messages in queue"); } else { while (msgs.hasMoreElements()) { Message tempMsg = (Message)msgs.nextElement(); System.out.println("Message: " + tempMsg); } }
Closes the connection, which automatically closes the session and QueueBrowser.
The format in which the message contents appear is implementation-specific. In the Application Server, the message format looks like this:
Message contents: Text: This is message 3 Class: com.sun.messaging.jmq.jmsclient.TextMessageImpl getJMSMessageID(): ID:14-129.148.71.199(f9:86:a2:d5:46:9b)-40814-1129061034355 getJMSTimestamp(): 1129061034355 getJMSCorrelationID(): null JMSReplyTo: null JMSDestination: PhysicalQueue getJMSDeliveryMode(): PERSISTENT getJMSRedelivered(): false getJMSType(): null getJMSExpiration(): 0 getJMSPriority(): 4 Properties: null |
You will use the connection factory and queue you created in Creating JMS Administered Objects for the Synchronous Receive Example.
To compile and package the MessageBrowser example using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.
Select the messagebrowser folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the MessageBrowser example using Ant, follow these steps:
In a terminal window, go to the messagebrowser directory. If you are currently in the asynchconsumer/dist directory, you need to go up two levels:
cd ../../messagebrowser |
Type the following command:
ant |
The targets place the application client JAR file in the dist directory for the example.
You also need the Producer example to send the message to the queue, and one of the consumer programs to consume the messages after you inspect them. If you did not do so already, package these examples.
To run the programs using NetBeans IDE, follow these steps.
Run the Producer program, sending one message to the queue:
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 |
Run the MessageBrowser program. Right-click the messagebrowser project and choose Run.
The output of the program looks like this:
Message: Text: This is message 1 Class: com.sun.messaging.jmq.jmsclient.TextMessageImpl getJMSMessageID(): ID:12-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957611 getJMSTimestamp(): 1129062957611 getJMSCorrelationID(): null JMSReplyTo: null JMSDestination: PhysicalQueue getJMSDeliveryMode(): PERSISTENT getJMSRedelivered(): false getJMSType(): null getJMSExpiration(): 0 getJMSPriority(): 4 Properties: null Message: Class: com.sun.messaging.jmq.jmsclient.MessageImpl getJMSMessageID(): ID:13-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957616 getJMSTimestamp(): 1129062957616 getJMSCorrelationID(): null JMSReplyTo: null JMSDestination: PhysicalQueue getJMSDeliveryMode(): PERSISTENT getJMSRedelivered(): false getJMSType(): null getJMSExpiration(): 0 getJMSPriority(): 4 Properties: null |
The first message is the TextMessage, and the second is the non-text control message.
Run the SynchConsumer program to consume the messages.
Right-click the synchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue |
Click OK.
Right-click the project and choose Run.
The output of the program looks like this:
Destination type is queue Reading message: This is message 1 |
To run the clients using the appclient command, follow these steps. You may want to use two terminal windows.
Go to the producer/dist directory.
Run the Producer program, sending one message to the queue:
appclient -client producer.jar queue |
The output of the program looks like this:
Destination type is queue Sending message: This is message 1 |
Go to the messagebrowser/dist directory.
Run the MessageBrowser program:
appclient -client messagebrowser.jar |
The output of the program looks like this:
Message: Text: This is message 1 Class: com.sun.messaging.jmq.jmsclient.TextMessageImpl getJMSMessageID(): ID:12-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957611 getJMSTimestamp(): 1129062957611 getJMSCorrelationID(): null JMSReplyTo: null JMSDestination: PhysicalQueue getJMSDeliveryMode(): PERSISTENT getJMSRedelivered(): false getJMSType(): null getJMSExpiration(): 0 getJMSPriority(): 4 Properties: null Message: Class: com.sun.messaging.jmq.jmsclient.MessageImpl getJMSMessageID(): ID:13-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957616 getJMSTimestamp(): 1129062957616 getJMSCorrelationID(): null JMSReplyTo: null JMSDestination: PhysicalQueue getJMSDeliveryMode(): PERSISTENT getJMSRedelivered(): false getJMSType(): null getJMSExpiration(): 0 getJMSPriority(): 4 Properties: null |
The first message is the TextMessage, and the second is the non-text control message.
Go to the synchconsumer/dist directory.
Run the SynchConsumer program to consume the messages:
appclient -client synchconsumer.jar queue |
The output of the program looks like this:
Destination type is queue Reading message: This is message 1 |
JMS client programs using the Application Server can exchange messages with each other when they are running on different systems in a network. The systems must be visible to each other by name (the UNIX host name or the Microsoft Windows computer name) and must both be running the Application Server. You do not have to install the tutorial examples on both systems; you can use the examples installed on one system if you can access its file system from the other system.
Any mechanism for exchanging messages between systems is specific to the Java EE server implementation. This tutorial describes how to use the Application Server for this purpose.
Suppose that you want to run the Producer program on one system, earth, and the SynchConsumer program on another system, jupiter. Before you can do so, you need to perform these tasks:
Create two new connection factories
Edit the source code for the two examples
Recompile and repackage the examples
A limitation in the JMS provider in the Application Server may cause a runtime failure to create a connection to systems that use the Dynamic Host Configuration Protocol (DHCP) to obtain an IP address. You can, however, create a connection from a system that uses DHCP to a system that does not use DHCP. In the examples in this tutorial, earth can be a system that uses DHCP, and jupiter can be a system that does not use DHCP.
Before you begin, start the server on both systems:
Start the Application Server on earth.
Start the Application Server on jupiter.
To run these programs, you must do the following:
Create a new connection factory on both earth and jupiter
Create a destination resource on both earth and jupiter
You do not have to install the tutorial on both systems, but you must be able to access the filesystem where it is installed. You may find it more convenient to install the tutorial on both systems if the two systems use different operating systems (for example, Windows and Solaris). Otherwise you will have to edit the file tut-install/javaeetutorial5/examples/bp-project/build.properties and change the location of the javaee.home property each time you build or run a program on a different system.
To create a new connection factory on jupiter, perform these steps:
From a command shell on jupiter, go to the directory tut-install/javaeetutorial5/examples/jms/simple/producer/.
Type the following command:
ant create-local-factory |
The create-local-factory target, defined in the build.xml file for the Producer example, creates a connection factory named jms/JupiterConnectionFactory.
To create a new connection factory on earth that points to the connection factory on jupiter, perform these steps:
From a command shell on earth, go to the directory tut-install/javaeetutorial5/examples/jms/simple/producer/.
Type the following command:
ant create-remote-factory -Dsys=remote-system-name |
Replace remote-system-name with the actual name of the remote system.
The create-remote-factory target, defined in the build.xml file for the Producer example, also creates a connection factory named jms/JupiterConnectionFactory. In addition, it sets the AddressList property for this factory to the name of the remote system.
If you have already been working on either earth or jupiter, you have the queue and topic on one system. On the system that does not have the queue and topic, type the following command:
ant create-resources |
When you run the programs, they will work as shown in Figure 31–6. The program run on earth needs the queue on earth only in order that the resource injection will succeed. The connection, session, and message producer are all created on jupiter using the connection factory that points to jupiter. The messages sent from earth will be received on jupiter.
These steps assume that you have the tutorial installed on only one of the two systems you are using and that you are able to access the file system of jupiter from earth or vice versa.
After you create the connection factories, edit the source files to specify the new connection factory. Then recompile, repackage, and run the programs. Perform the following steps:
Open the following file in a text editor:
tut-installjavaeetutorial5/examples/jms/simple/producer/src/java/Producer.java
Find the following line:
@Resource(mappedName="jms/ConnectionFactory")
Change the line to the following:
@Resource(mappedName="jms/JupiterConnectionFactory")
Recompile and repackage the Producer example on earth.
If you are using NetBeans IDE, right-click the producer project and choose Clean and Build.
If you are using Ant, type the following:
ant |
Open the following file in a text editor:
tut-installjavaeetutorial5/examples/jms/simple/synchconsumer/src/java/SynchConsumer.java
Repeat steps 2 and 3.
Recompile and repackage the SynchConsumer example on jupiter.
If you are using NetBeans IDE, right-click the synchconsumer project and choose Clean and Build.
If you are using Ant, go to the synchconsumer directory and type:
ant |
On earth, run Producer. If you are using NetBeans IDE on earth, perform these steps:
Right-click the producer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue 3 |
Click OK.
Right-click the project and choose Run.
If you are using the appclient command, go to the producer/dist directory and type the following:
appclient -client producer.jar queue 3 |
On jupiter, run SynchConsumer. If you are using NetBeans IDE on jupiter, perform these steps:
Right-click the synchconsumer project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type the following:
queue |
Click OK.
Right-click the project and choose Run.
If you are using the appclient command, go to the synchconsumer/dist directory and type the following:
appclient -client synchconsumer.jar queue |
For examples showing how to deploy Java EE applications on two different systems, see An Application Example That Consumes Messages from a Remote Server and An Application Example That Deploys a Message-Driven Bean on Two Servers.
You will need the connection factory jms/JupiterConnectionFactory in Chapter 32, Java EE Examples Using the JMS API. However, if you wish to delete it, go to the producer directory and type the following command:
ant delete-remote-factory |
Remember to delete the connection factory on both systems.
You can also use Ant targets in the producer/build.xml file to delete the destinations and connection factories you created in Creating JMS Administered Objects for the Synchronous Receive Example. However, it is recommended that you keep them, because they will be used in most of the examples in Chapter 32, Java EE Examples Using the JMS API. After you have created them, they will be available whenever you restart the Application Server.
To delete the class and JAR files for each program using NetBeans IDE, right-click each project and choose Clean.
To delete the class and JAR files for each program using Ant, type the following:
ant clean |
You can also stop the Application Server, but you will need it to run the sample programs in the next section.
This section explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many people choose to implement JMS applications because they cannot tolerate dropped or duplicate messages and require that every message be received once and only once. The JMS API provides this functionality.
The most reliable way to produce a message is to send a PERSISTENT message within a transaction. JMS messages are PERSISTENT by default. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Specifying Message Persistence and Using JMS API Local Transactions.
The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic. For details, see Creating Temporary Destinations, Creating Durable Subscriptions, and Using JMS API Local Transactions.
For other applications, a lower level of reliability can reduce overhead and improve performance. You can send messages with varying priority levels (see Setting Message Priority Levels) and you can set them to expire after a certain length of time (see Allowing Messages to Expire).
The JMS API provides several ways to achieve various kinds and degrees of reliability. This section divides them into two categories:
The following sections describe these features as they apply to JMS clients. Some of the features work differently in Java EE applications; in these cases, the differences are noted here and are explained in detail in Using the JMS API in a Java EE Application.
This section includes three sample programs, which you can find in the directory tut-install/javaeetutorial5/examples/jms/advanced/. Each sample uses a utility class called SampleUtilities.java.
The basic mechanisms for achieving or affecting reliable message delivery are as follows:
Controlling message acknowledgment: You can specify various levels of control over message acknowledgment.
Specifying message persistence: You can specify that messages are persistent, meaning that they must not be lost in the event of a provider failure.
Setting message priority levels: You can set various priority levels for messages, which can affect the order in which the messages are delivered.
Allowing messages to expire: You can specify an expiration time for messages so that they will not be delivered if they are obsolete.
Creating temporary destinations: You can create temporary destinations that last only for the duration of the connection in which they are created.
Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.
The client receives the message.
The client processes the message.
The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.
In transacted sessions (see Using JMS API Local Transactions), acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered.
In nontransacted sessions, when and how a message is acknowledged depend on the value specified as the second argument of the createSession method. The three possible argument values are as follows:
Session.AUTO_ACKNOWLEDGE: The session automatically acknowledges a client’s receipt of a message either when the client has successfully returned from a call to receive or when the MessageListener it has called to process the message returns successfully. A synchronous receive in an AUTO_ACKNOWLEDGE session is the one exception to the rule that message consumption is a three-stage process as described earlier.
In this case, the receipt and acknowledgment take place in one step, followed by the processing of the message.
Session.CLIENT_ACKNOWLEDGE: A client acknowledges a message by calling the message’s acknowledge method. In this mode, acknowledgment takes place on the session level: Acknowledging a consumed message automatically acknowledges the receipt of all messages that have been consumed by its session. For example, if a message consumer consumes ten messages and then acknowledges the fifth message delivered, all ten messages are acknowledged.
Session.DUPS_OK_ACKNOWLEDGE: This option instructs the session to lazily acknowledge the delivery of messages. This is likely to result in the delivery of some duplicate messages if the JMS provider fails, so it should be used only by consumers that can tolerate duplicate messages. (If the JMS provider redelivers a message, it must set the value of the JMSRedelivered message header to true.) This option can reduce session overhead by minimizing the work the session does to prevent duplicates.
If messages have been received from a queue but not acknowledged when a session terminates, the JMS provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages for a terminated session that has a durable TopicSubscriber. (See Creating Durable Subscriptions.) Unacknowledged messages for a nondurable TopicSubscriber are dropped when the session is closed.
If you use a queue or a durable subscription, you can use the Session.recover method to stop a nontransacted session and restart it with its first unacknowledged message. In effect, the session’s series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or if higher-priority messages have arrived. For a nondurable TopicSubscriber, the provider may drop unacknowledged messages when its session is recovered.
The sample program in the next section demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.
The AckEquivExample.java program shows how both of the following two scenarios ensure that a message will not be acknowledged until processing of it is complete:
Using an asynchronous message consumer (a message listener) in an AUTO_ACKNOWLEDGE session
Using a synchronous receiver in a CLIENT_ACKNOWLEDGE session
With a message listener, the automatic acknowledgment happens when the onMessage method returns (that is, after message processing has finished). With a synchronous receiver, the client acknowledges the message after processing is complete. If you use AUTO_ACKNOWLEDGE with a synchronous receive, the acknowledgment happens immediately after the receive call; if any subsequent processing steps fail, the message cannot be redelivered.
The program is in the following directory:
tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/src/java/
The program contains a SynchSender class, a SynchReceiver class, an AsynchSubscriber class with a TextListener class, a MultiplePublisher class, a main method, and a method that runs the other classes’ threads.
The program uses the following objects:
jms/ConnectionFactory, jms/Queue, and jms/Topic: resources that you created in Creating JMS Administered Objects for the Synchronous Receive Example
jms/ControlQueue: an additional queue
jms/DurableConnectionFactory: a connection factory with a client ID (see Creating Durable Subscriptions, for more information)
To create the new queue and connection factory, you can use Ant targets defined in the file tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/build.xml.
To run this example, follow these steps:
In a terminal window, go to the following directory:
tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/
To create the objects needed in this example, type the following commands:
ant create-control-queue ant create-durable-cf |
To compile and package the program using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.
Select the ackequivexample folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the program using Ant, type the following command:
ant |
To run the program using NetBeans IDE, right-click the ackequivexample project and choose Run.
To run the program from the command line, follow these steps:
Go to the dist directory:
cd dist |
Type the following command:
appclient -client ackequivexample.jar |
The program output looks something like this:
Queue name is jms/ControlQueue Queue name is jms/Queue Topic name is jms/Topic Connection factory name is jms/DurableConnectionFactory SENDER: Created client-acknowledge session SENDER: Sending message: Here is a client-acknowledge message RECEIVER: Created client-acknowledge session RECEIVER: Processing message: Here is a client-acknowledge message RECEIVER: Now I’ll acknowledge the message SUBSCRIBER: Created auto-acknowledge session SUBSCRIBER: Sending synchronize message to control queue PUBLISHER: Created auto-acknowledge session PUBLISHER: Receiving synchronize messages from control queue; count = 1 PUBLISHER: Received synchronize message; expect 0 more PUBLISHER: Publishing message: Here is an auto-acknowledge message 1 PUBLISHER: Publishing message: Here is an auto-acknowledge message 2 SUBSCRIBER: Processing message: Here is an auto-acknowledge message 1 PUBLISHER: Publishing message: Here is an auto-acknowledge message 3 SUBSCRIBER: Processing message: Here is an auto-acknowledge message 2 SUBSCRIBER: Processing message: Here is an auto-acknowledge message 3 |
After you run the program, you can delete the destination resource jms/ControlQueue. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/ and type the following command:
ant delete-control-queue |
You will need the other resources for other examples.
To delete the class and JAR files for the program using NetBeans IDE, right-click the project and choose Clean.
To delete the class and JAR files for the program using Ant, type the following:
ant clean |
The JMS API supports two delivery modes for messages to specify whether messages are lost if the JMS provider fails. These delivery modes are fields of the DeliveryMode interface.
The PERSISTENT delivery mode, which is the default, instructs the JMS provider to take extra care to ensure that a message is not lost in transit in case of a JMS provider failure. A message sent with this delivery mode is logged to stable storage when it is sent.
The NON_PERSISTENT delivery mode does not require the JMS provider to store the message or otherwise guarantee that it is not lost if the provider fails.
You can specify the delivery mode in either of two ways.
You can use the setDeliveryMode method of the MessageProducer interface to set the delivery mode for all messages sent by that producer. For example, the following call sets the delivery mode to NON_PERSISTENT for a producer:
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
You can use the long form of the send or the publish method to set the delivery mode for a specific message. The second argument sets the delivery mode. For example, the following send call sets the delivery mode for message to NON_PERSISTENT:
producer.send(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
The third and fourth arguments set the priority level and expiration time, which are described in the next two subsections.
If you do not specify a delivery mode, the default is PERSISTENT. Using the NON_PERSISTENT delivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.
You can use message priority levels to instruct the JMS provider to deliver urgent messages first. You can set the priority level in either of two ways.
You can use the setPriority method of the MessageProducer interface to set the priority level for all messages sent by that producer. For example, the following call sets a priority level of 7 for a producer:
producer.setPriority(7);
You can use the long form of the send or the publish method to set the priority level for a specific message. The third argument sets the priority level. For example, the following send call sets the priority level for message to 3:
producer.send(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones but does not have to deliver messages in exact order of priority.
By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. You can do this in either of two ways.
You can use the setTimeToLive method of the MessageProducer interface to set a default expiration time for all messages sent by that producer. For example, the following call sets a time to live of one minute for a producer:
producer.setTimeToLive(60000);
You can use the long form of the send or the publish method to set an expiration time for a specific message. The fourth argument sets the expiration time in milliseconds. For example, the following send call sets a time to live of 10 seconds:
producer.send(message, DeliveryMode.NON_PERSISTENT, 3, 10000);
If the specified timeToLive value is 0, the message never expires.
When the message is sent, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.
Normally, you create JMS destinations (queues and topics) administratively rather than programmatically. Your JMS provider includes a tool that you use to create and remove destinations, and it is common for destinations to be long-lasting.
The JMS API also enables you to create destinations (TemporaryQueue and TemporaryTopic objects) that last only for the duration of the connection in which they are created. You create these destinations dynamically using the Session.createTemporaryQueue and the Session.createTemporaryTopic methods.
The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection that a temporary destination belongs to, the destination is closed and its contents are lost.
You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the JMSReplyTo message header field when you send a message, then the consumer of the message can use the value of the JMSReplyTo field as the destination to which it sends a reply. The consumer can also reference the original request by setting the JMSCorrelationID header field of the reply message to the value of the JMSMessageID header field of the request. For example, an onMessage method can create a session so that it can send a reply to the message it receives. It can use code such as the following:
producer = session.createProducer(msg.getJMSReplyTo()); replyMsg = session.createTextMessage("Consumer " + "processed message: " + msg.getText()); replyMsg.setJMSCorrelationID(msg.getJMSMessageID()); producer.send(replyMsg);
For more examples, see Chapter 32, Java EE Examples Using the JMS API.
The more advanced mechanisms for achieving reliable message delivery are the following:
Creating durable subscriptions: You can create durable topic subscriptions, which receive messages published while the subscriber is not active. Durable subscriptions offer the reliability of queues to the publish/subscribe message domain.
Using local transactions: You can use local transactions, which allow you to group a series of sends and receives into an atomic unit of work. Transactions are rolled back if they fail at any time.
To ensure that a pub/sub application receives all published messages, use PERSISTENT delivery mode for the publishers. In addition, use durable subscriptions for the subscribers.
The Session.createConsumer method creates a nondurable subscriber if a topic is specified as the destination. A nondurable subscriber can receive only messages that are published while it is active.
At the cost of higher overhead, you can use the Session.createDurableSubscriber method to create a durable subscriber. A durable subscription can have only one active subscriber at a time.
A durable subscriber registers a durable subscription by specifying a unique identity that is retained by the JMS provider. Subsequent subscriber objects that have the same identity resume the subscription in the state in which it was left by the preceding subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription’s messages until they are received by the subscription or until they expire.
You establish the unique identity of a durable subscriber by setting the following:
A client ID for the connection
A topic and a subscription name for the subscriber
You set the client ID administratively for a client-specific connection factory using the Admin Console.
After using this connection factory to create the connection and the session, you call the createDurableSubscriber method with two arguments: the topic and a string that specifies the name of the subscription:
String subName = "MySub"; MessageConsumer topicSubscriber = session.createDurableSubscriber(myTopic, subName);
The subscriber becomes active after you start the Connection or TopicConnection. Later, you might close the subscriber:
topicSubscriber.close();
The JMS provider stores the messages sent or published to the topic, as it would store messages sent to a queue. If the program or another application calls createDurableSubscriber using the same connection factory and its client ID, the same topic, and the same subscription name, the subscription is reactivated, and the JMS provider delivers the messages that were published while the subscriber was inactive.
To delete a durable subscription, first close the subscriber, and then use the unsubscribe method, with the subscription name as the argument:
topicSubscriber.close(); session.unsubscribe("MySub");
The unsubscribe method deletes the state that the provider maintains for the subscriber.
Figure 31–7 and Figure 31–8 show the difference between a nondurable and a durable subscriber. With an ordinary, nondurable subscriber, the subscriber and the subscription begin and end at the same point and are, in effect, identical. When a subscriber is closed, the subscription also ends. Here, create stands for a call to Session.createConsumer with a Topic argument, and close stands for a call to MessageConsumer.close. Any messages published to the topic between the time of the first close and the time of the second create are not consumed by the subscriber. In Figure 31–7, the subscriber consumes messages M1, M2, M5, and M6, but messages M3 and M4 are lost.
With a durable subscriber, the subscriber can be closed and re-created, but the subscription continues to exist and to hold messages until the application calls the unsubscribe method. In Figure 31–8, create stands for a call to Session.createDurableSubscriber, close stands for a call to MessageConsumer.close, and unsubscribe stands for a call to Session.unsubscribe. Messages published while the subscriber is closed are received when the subscriber is created again. So even though messages M2, M4, and M5 arrive while the subscriber is closed, they are not lost.
See A Java EE Application That Uses the JMS API with a Session Bean for an example of a Java EE application that uses durable subscriptions. See A Message Acknowledgment Example and the next section for examples of client applications that use durable subscriptions.
The DurableSubscriberExample.java program shows how durable subscriptions work. It demonstrates that a durable subscription is active even when the subscriber is not active. The program contains a DurableSubscriber class, a MultiplePublisher class, a main method, and a method that instantiates the classes and calls their methods in sequence.
The program is in the following directory:
tut-install/javaeetutorial5/examples/jms/advanced/durablesubscriberexample/src/java/
The program begins in the same way as any publish/subscribe program: The subscriber starts, the publisher publishes some messages, and the subscriber receives them. At this point, the subscriber closes itself. The publisher then publishes some messages while the subscriber is not active. The subscriber then restarts and receives the messages.
Before you run this program, compile and package the source file and create a connection factory that has a client ID. Perform the following steps:
To compile and package the program using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.
Select the durablesubscriberexample folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the program using Ant, follow these steps:
If you did not do so for A Message Acknowledgment Example, create a connection factory named jms/DurableConnectionFactory:
ant create-durable-cf |
To run the program using NetBeans IDE, right-click the durablesubscriberexample project and choose Run.
To run the program from the command line, follow these steps:
Go to the dist directory:
cd dist |
Type the following command:
appclient -client durablesubscriberexample.jar |
The output looks something like this:
Connection factory without client ID is jms/ConnectionFactory Connection factory with client ID is jms/DurableConnectionFactory Topic name is jms/Topic Starting subscriber PUBLISHER: Publishing message: Here is a message 1 SUBSCRIBER: Reading message: Here is a message 1 PUBLISHER: Publishing message: Here is a message 2 SUBSCRIBER: Reading message: Here is a message 2 PUBLISHER: Publishing message: Here is a message 3 SUBSCRIBER: Reading message: Here is a message 3 Closing subscriber PUBLISHER: Publishing message: Here is a message 4 PUBLISHER: Publishing message: Here is a message 5 PUBLISHER: Publishing message: Here is a message 6 Starting subscriber SUBSCRIBER: Reading message: Here is a message 4 SUBSCRIBER: Reading message: Here is a message 5 SUBSCRIBER: Reading message: Here is a message 6 Closing subscriber Unsubscribing from durable subscription |
After you run the program, you can delete the connection factory jms/DurableConnectionFactory. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/durablesubscriberexample/ and type the following command:
ant delete-durable-cf |
To delete the class and JAR files for the program using NetBeans IDE, right-click the project and choose Clean.
To delete the class and JAR files for the program using Ant, type the following:
ant clean |
You can group a series of operations into an atomic unit of work called a transaction. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.
In a JMS client, you can use local transactions to group message sends and receives. The JMS API Session interface provides commit and rollback methods that you can use in a JMS client. A transaction commit means that all produced messages are sent and all consumed messages are acknowledged. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Allowing Messages to Expire).
A transacted session is always involved in a transaction. As soon as the commit or the rollback method is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.
In an Enterprise JavaBeans component, you cannot use the Session.commit and Session.rollback methods. Instead, you use distributed transactions, which are described in Using the JMS API in a Java EE Application.
You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request/reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:
// Don’t do this! outMsg.setJMSReplyTo(replyQueue); producer.send(outQueue, outMsg); consumer = session.createConsumer(replyQueue); inMsg = consumer.receive(); session.commit();
Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message’s having been sent.
In addition, the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 31–9 illustrates this interaction.
The sending of one or more messages to one or more destinations by client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider using a single session. Similarly, the receiving of one or more messages from one or more destinations by client 2 also forms a single transaction using a single session. But because the two clients have no direct interaction and are using two different sessions, no transactions can take place between them.
Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.
This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider’s ability to supply a once-and-only-once message delivery guarantee.
When you create a session, you specify whether it is transacted. The first argument to the createSession method is a boolean value. A value of true means that the session is transacted; a value of false means that it is not transacted. The second argument to this method is the acknowledgment mode, which is relevant only to nontransacted sessions (see Controlling Message Acknowledgment). If the session is transacted, the second argument is ignored, so it is a good idea to specify 0 to make the meaning of your code clear. For example:
session = connection.createSession(true, 0);
The commit and the rollback methods for local transactions are associated with the session. You can combine queue and topic operations in a single transaction if you use the same session to perform the operations. For example, you can use the same session to receive a message from a queue and send a message to a topic in the same transaction.
You can pass a client program’s session to a message listener’s constructor function and use it to create a message producer. In this way, you can use the same session for receives and sends in asynchronous message consumers.
The next section provides an example of the use of JMS API local transactions.
The TransactedExample.java program demonstrates the use of transactions in a JMS client application. The program is in the following directory:
tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/src/java/
This example shows how to use a queue and a topic in a single transaction as well as how to pass a session to a message listener’s constructor function. The program represents a highly simplified e-commerce application in which the following things happen.
A retailer sends a MapMessage to the vendor order queue, ordering a quantity of computers, and waits for the vendor’s reply:
producer = session.createProducer(vendorOrderQueue); outMessage = session.createMapMessage(); outMessage.setString("Item", "Computer(s)"); outMessage.setInt("Quantity", quantity); outMessage.setJMSReplyTo(retailerConfirmQueue); producer.send(outMessage); System.out.println("Retailer: ordered " + quantity + " computer(s)"); orderConfirmReceiver = session.createConsumer(retailerConfirmQueue); connection.start();
The vendor receives the retailer’s order message and sends an order message to the supplier order topic in one transaction. This JMS transaction uses a single session, so you can combine a receive from a queue with a send to a topic. Here is the code that uses the same session to create a consumer for a queue and a producer for a topic:
vendorOrderReceiver = session.createConsumer(vendorOrderQueue); supplierOrderProducer = session.createProducer(supplierOrderTopic);
The following code receives the incoming message, sends an outgoing message, and commits the session. The message processing has been removed to keep the sequence simple:
inMessage = vendorOrderReceiver.receive(); // Process the incoming message and format the outgoing // message ... supplierOrderProducer.send(orderMessage); ... session.commit();
Each supplier receives the order from the order topic, checks its inventory, and then sends the items ordered to the queue named in the order message’s JMSReplyTo field. If it does not have enough in stock, the supplier sends what it has. The synchronous receive from the topic and the send to the queue take place in one JMS transaction.
receiver = session.createConsumer(orderTopic); ... inMessage = receiver.receive(); if (inMessage instanceof MapMessage) { orderMessage = (MapMessage) inMessage; } // Process message MessageProducer producer = session.createProducer((Queue) orderMessage.getJMSReplyTo()); outMessage = session.createMapMessage(); // Add content to message producer.send(outMessage); // Display message contentssession.commit();
The vendor receives the replies from the suppliers from its confirmation queue and updates the state of the order. Messages are processed by an asynchronous message listener; this step shows the use of JMS transactions with a message listener.
MapMessage component = (MapMessage) message; ... orderNumber = component.getInt("VendorOrderNumber"); Order order = Order.getOrder(orderNumber).processSubOrder(component); session.commit();
When all outstanding replies are processed for a given order, the vendor message listener sends a message notifying the retailer whether it can fulfill the order.
Queue replyQueue = (Queue) order.order.getJMSReplyTo(); MessageProducer producer = session.createProducer(replyQueue); MapMessage retailerConfirmMessage = session.createMapMessage(); // Format the message producer.send(retailerConfirmMessage); session.commit();
The retailer receives the message from the vendor:
inMessage = (MapMessage) orderConfirmReceiver.receive();
Figure 31–10 illustrates these steps.
The program contains five classes: Retailer, Vendor, GenericSupplier, VendorMessageListener, and Order. The program also contains a main method and a method that runs the threads of the Retailer, Vendor, and two supplier classes.
All the messages use the MapMessage message type. Synchronous receives are used for all message reception except for the case of the vendor processing the replies of the suppliers. These replies are processed asynchronously and demonstrate how to use transactions within a message listener.
At random intervals, the Vendor class throws an exception to simulate a database problem and cause a rollback.
All classes except Retailer use transacted sessions.
The program uses three queues named jms/AQueue, jms/BQueue, and jms/CQueue, and one topic named jms/OTopic.
Before you run the program, do the following:
In a terminal window, go to the following directory:
tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/
Create the necessary resources using the following command:
ant create-resources |
This command creates three destination resources with the names jms/AQueue, jms/BQueue, and jms/CQueue, all of type javax.jms.Queue, and one destination resource with the name jms/OTopic, of type javax.jms.Topic.
To compile and package the program using NetBeans IDE, follow these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.
Select the transactedexample folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the project and choose Build.
To compile and package the program using Ant, follow these steps:
To run the program using NetBeans IDE, follow these steps:
Right-click the transactedexample project and choose Properties.
Select Run from the Categories tree.
In the Arguments field, type a number that specifies the number of computers to order:
3
Click OK.
Right-click the project and choose Run.
To run the program from the command line, follow these steps:
Go to the dist directory:
cd dist |
Use a command like the following to run the program. The argument specifies the number of computers to order:
appclient -client transactedexample.jar 3 |
The output looks something like this:
Quantity to be ordered is 3 Retailer: ordered 3 computer(s) Vendor: Retailer ordered 3 Computer(s) Vendor: ordered 3 monitor(s) and hard drive(s) Monitor Supplier: Vendor ordered 3 Monitor(s) Monitor Supplier: sent 3 Monitor(s) Monitor Supplier: committed transaction Vendor: committed transaction 1 Hard Drive Supplier: Vendor ordered 3 Hard Drive(s) Hard Drive Supplier: sent 1 Hard Drive(s) Vendor: Completed processing for order 1 Hard Drive Supplier: committed transaction Vendor: unable to send 3 computer(s) Vendor: committed transaction 2 Retailer: Order not filled Retailer: placing another order Retailer: ordered 6 computer(s) Vendor: JMSException occurred: javax.jms.JMSException: Simulated database concurrent access exception javax.jms.JMSException: Simulated database concurrent access exception at TransactedExample$Vendor.run(Unknown Source) Vendor: rolled back transaction 1 Vendor: Retailer ordered 6 Computer(s) Vendor: ordered 6 monitor(s) and hard drive(s) Monitor Supplier: Vendor ordered 6 Monitor(s) Hard Drive Supplier: Vendor ordered 6 Hard Drive(s) Monitor Supplier: sent 6 Monitor(s) Monitor Supplier: committed transaction Hard Drive Supplier: sent 6 Hard Drive(s) Hard Drive Supplier: committed transaction Vendor: committed transaction 1 Vendor: Completed processing for order 2 Vendor: sent 6 computer(s) Retailer: Order filled Vendor: committed transaction 2 |
After you run the program, you can delete the physical destinations and the destination resources. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/ and type the following command:
ant delete-resources |
Use the following command to remove the class and JAR files:
ant clean |
This section describes the ways in which using the JMS API in a Java EE application differs from using it in a stand-alone client application:
Using Session Beans to Produce and to Synchronously Receive Messages
Using Message-Driven Beans to Receive Messages Asynchronously
Using the JMS API with Application Clients and Web Components
A general rule in the Java EE platform specification applies to all Java EE components that use the JMS API within EJB or web containers:
Application components in the web and EJB containers must not attempt to create more than one active (not closed) Session object per connection.
This rule does not apply to application clients.
When you use the @Resource annotation in an application client component, you normally declare the JMS resource static:
@Resource(mappedName="jms/ConnectionFactory") private static ConnectionFactory connectionFactory; @Resource(mappedName="jms/Queue") private static Queue queue;
However, when you use this annotation in a session bean, a message-driven bean, or a web component, do not declare the resource static:
@Resource(mappedName="jms/ConnectionFactory") private ConnectionFactory connectionFactory; @Resource(mappedName="jms/Topic") private Topic topic;
If you declare the resource static, runtime errors will result.
A Java EE application that produces messages or synchronously receives them can use a session bean to perform these operations. The example in A Java EE Application That Uses the JMS API with a Session Bean uses a stateless session bean to publish messages to a topic.
Because a blocking synchronous receive ties up server resources, it is not a good programming practice to use such a receive call in an enterprise bean. Instead, use a timed synchronous receive, or use a message-driven bean to receive messages asynchronously. For details about blocking and timed synchronous receives, see Writing the Client Programs for the Synchronous Receive Example.
Using the JMS API in a Java EE application is in many ways similar to using it in a stand-alone client. The main differences are in resource management and transactions.
The JMS API resources are a JMS API connection and a JMS API session. In general, it is important to release JMS resources when they are no longer being used. Here are some useful practices to follow.
If you wish to maintain a JMS API resource only for the life span of a business method, it is a good idea to close the resource in a finally block within the method.
If you would like to maintain a JMS API resource for the life span of an enterprise bean instance, it is a good idea to use a @PostConstruct callback method to create the resource and to use a @PreDestroy callback method to close the resource. If you use a stateful session bean and you wish to maintain the JMS API resource in a cached state, you must close the resource in a @PrePassivate callback method and set its value to null, and you must create it again in a @PostActivate callback method.
Instead of using local transactions, you use container-managed transactions for bean methods that perform sends or receives, allowing the EJB container to handle transaction demarcation. Because container-managed transactions are the default, you do not have to use an annotation to specify them.
You can use bean-managed transactions and the javax.transaction.UserTransaction interface’s transaction demarcation methods, but you should do so only if your application has special requirements and you are an expert in using transactions. Usually, container-managed transactions produce the most efficient and correct behavior. This tutorial does not provide any examples of bean-managed transactions.
The sections What Is a Message-Driven Bean? and How Does the JMS API Work with the Java EE Platform? describe how the Java EE platform supports a special kind of enterprise bean, the message-driven bean, which allows Java EE applications to process JMS messages asynchronously. Session beans allow you to send messages and to receive them synchronously but not asynchronously.
A message-driven bean is a message listener that can reliably consume messages from a queue or a durable subscription. The messages can be sent by any Java EE component (from an application client, another enterprise bean, or a web component) or from an application or a system that does not use Java EE technology.
Like a message listener in a stand-alone JMS client, a message-driven bean contains an onMessage method that is called automatically when a message arrives. Like a message listener, a message-driven bean class can implement helper methods invoked by the onMessage method to aid in message processing.
A message-driven bean, however, differs from a stand-alone client’s message listener in the following ways:
Certain setup tasks are performed by the EJB container.
The bean class uses the @MessageDriven annotation to specify properties for the bean or the connection factory, such as a destination type, a durable subscription, a message selector, or an acknowledgment mode. The examples in Chapter 32, Java EE Examples Using the JMS API show how the JMS resource adapter works in the Application Server.
The EJB container automatically performs several setup tasks that a stand-alone client has to do:
Creating a message consumer to receive the messages. Instead of creating a message consumer in your source code, you associate the message-driven bean with a destination and a connection factory at deployment time. If you want to specify a durable subscription or use a message selector, you do this at deployment time also.
Registering the message listener. You must not call setMessageListener.
Specifying a message acknowledgment mode. The default mode, AUTO_ACKNOWLEDGE, is used unless it is overriden by a property setting.
If JMS is integrated with the application server using a resource adapter, the JMS resource adapter handles these tasks for the EJB container.
Your message-driven bean class must implement the javax.jms.MessageListener interface and the onMessage method.
It may implement a @PostConstruct callback method to create a connection, and a @PreDestroy callback method to close the connection. Typically, it implements these methods if it produces messages or does synchronous receives from another destination.
The bean class commonly injects a MessageDrivenContext resource, which provides some additional methods that you can use for transaction management.
The main difference between a message-driven bean and a session bean is that a message-driven bean has no local or remote interface. Instead, it has only a bean class.
A message-driven bean is similar in some ways to a stateless session bean: Its instances are relatively short-lived and retain no state for a specific client. The instance variables of the message-driven bean instance can contain some state across the handling of client messages: for example, a JMS API connection, an open database connection, or an object reference to an enterprise bean object.
Like a stateless session bean, a message-driven bean can have many interchangeable instances running at the same time. The container can pool these instances to allow streams of messages to be processed concurrently. The container attempts to deliver messages in chronological order when it does not impair the concurrency of message processing, but no guarantees are made as to the exact order in which messages are delivered to the instances of the message-driven bean class. Because concurrency can affect the order in which messages are delivered, you should write your applications to handle messages that arrive out of sequence.
For example, your application could manage conversations by using application-level sequence numbers. An application-level conversation control mechanism with a persistent conversation state could cache later messages until earlier messages have been processed.
Another way to ensure order is to have each message or message group in a conversation require a confirmation message that the sender blocks on receipt of. This forces the responsibility for order back on the sender and more tightly couples senders to the progress of message-driven beans.
To create a new instance of a message-driven bean, the container does the following:
Instantiates the bean
Performs any required resource injection
Calls the @PostConstruct callback method, if it exists
To remove an instance of a message-driven bean, the container calls the @PreDestroy callback method.
Figure 31–11 shows the life cycle of a message-driven bean.
JMS client applications use JMS API local transactions (described in Using JMS API Local Transactions), which allow the grouping of sends and receives within a specific JMS session. Java EE applications commonly use distributed transactions to ensure the integrity of accesses to external resources. For example, distributed transactions allow multiple applications to perform atomic updates on the same database, and they allow a single application to perform atomic updates on multiple databases.
In a Java EE application that uses the JMS API, you can use transactions to combine message sends or receives with database updates and other resource manager operations. You can access resources from multiple application components within a single transaction. For example, a servlet can start a transaction, access multiple databases, invoke an enterprise bean that sends a JMS message, invoke another enterprise bean that modifies an EIS system using the Connector architecture, and finally commit the transaction. Your application cannot, however, both send a JMS message and receive a reply to it within the same transaction; the restriction described in Using JMS API Local Transactions still applies.
Distributed transactions within the EJB container can be either of two kinds:
Container-managed transactions: The EJB container controls the integrity of your transactions without your having to call commit or rollback. Container-managed transactions are recommended for Java EE applications that use the JMS API. You can specify appropriate transaction attributes for your enterprise bean methods.
Use the Required transaction attribute (the default) to ensure that a method is always part of a transaction. If a transaction is in progress when the method is called, the method will be part of that transaction; if not, a new transaction will be started before the method is called and will be committed when the method returns.
Bean-managed transactions: You can use these in conjunction with the javax.transaction.UserTransaction interface, which provides its own commit and rollback methods that you can use to delimit transaction boundaries. Bean-managed transactions are recommended only for those who are experienced in programming transactions.
You can use either container-managed transactions or bean-managed transactions with message-driven beans. To ensure that all messages are received and handled within the context of a transaction, use container-managed transactions and use the Required transaction attribute (the default) for the onMessage method. This means that if there is no transaction in progress, a new transaction will be started before the method is called and will be committed when the method returns.
When you use container-managed transactions, you can call the following MessageDrivenContext methods:
setRollbackOnly: Use this method for error handling. If an exception occurs, setRollbackOnly marks the current transaction so that the only possible outcome of the transaction is a rollback.
getRollbackOnly: Use this method to test whether the current transaction has been marked for rollback.
If you use bean-managed transactions, the delivery of a message to the onMessage method takes place outside the distributed transaction context. The transaction begins when you call the UserTransaction.begin method within the onMessage method, and it ends when you call UserTransaction.commit or UserTransaction.rollback. Any call to the Connection.createSession method must take place within the transaction. If you call UserTransaction.rollback, the message is not redelivered, whereas calling setRollbackOnly for container-managed transactions does cause a message to be redelivered.
Neither the JMS API specification nor the Enterprise JavaBeans specification (available from http://java.sun.com/products/ejb/) specifies how to handle calls to JMS API methods outside transaction boundaries. The Enterprise JavaBeans specification does state that the EJB container is responsible for acknowledging a message that is successfully processed by the onMessage method of a message-driven bean that uses bean-managed transactions. Using bean-managed transactions allows you to process the message by using more than one transaction or to have some parts of the message processing take place outside a transaction context. In most cases, however, container-managed transactions provide greater reliability and are therefore preferable.
When you create a session in an enterprise bean, the container ignores the arguments you specify, because it manages all transactional properties for enterprise beans. It is still a good idea to specify arguments of true and 0 to the createSession method to make this situation clear:
session = connection.createSession(true, 0);
When you use container-managed transactions, you normally use the Required transaction attribute (the default) for your enterprise bean’s business methods.
You do not specify a message acknowledgment mode when you create a message-driven bean that uses container-managed transactions. The container acknowledges the message automatically when it commits the transaction.
If a message-driven bean uses bean-managed transactions, the message receipt cannot be part of the bean-managed transaction, so the container acknowledges the message outside the transaction.
If the onMessage method throws a RuntimeException, the container does not acknowledge processing the message. In that case, the JMS provider will redeliver the unacknowledged message in the future.
An application client in a Java EE application can use the JMS API in much the same way that a stand-alone client program does. It can produce messages, and it can consume messages by using either synchronous receives or message listeners. See Chapter 23, A Message-Driven Bean Example for an example of an application client that produces messages. For an example of using an application client to produce and to consume messages, see An Application Example That Deploys a Message-Driven Bean on Two Servers.
The Java EE platform specification does not impose strict constraints on how web components should use the JMS API. In the Application Server, a web component can send messages and consume them synchronously but cannot consume them asynchronously.
Because a blocking synchronous receive ties up server resources, it is not a good programming practice to use such a receive call in a web component. Instead, use a timed synchronous receive. For details about blocking and timed synchronous receives, see Writing the Client Programs for the Synchronous Receive Example.
For more information about JMS, see:
Java Message Service web site:
Java Message Service specification, version 1.1, available from
This chapter provides examples that show how to use the JMS API within a Java EE application in the following ways:
Using a session bean to send messages that are consumed by a message-driven bean using a message selector and a durable subscription
Using an application client to send messages that are consumed by two message-driven beans; the information from them is stored in a Java Persistence API entity
Using an application client to send messages that are consumed by a message-driven bean on a remote server
Using an application client to send messages that are consumed by message-driven beans on two different servers
The examples are in the following directory:
tut-install/javaeetutorial5/examples/jms/
To build and run the examples, you will do the following:
Use NetBeans IDE or the Ant tool to compile and package the example.
Use the Ant tool to create resources.
Use NetBeans IDE or the Ant tool to deploy the example.
Use NetBeans IDE or the Ant tool to run the client.
Each example has a build.xml file that refers to files in the following directory:
tut-install/javaeetutorial5/examples/bp-project/
See Chapter 23, A Message-Driven Bean Example for a simpler example of a Java EE application that uses the JMS API.
This section explains how to write, compile, package, deploy, and run a Java EE application that uses the JMS API in conjunction with a session bean. The application contains the following components:
An application client that invokes a session bean
A session bean that publishes several messages to a topic
A message-driven bean that receives and processes the messages using a durable topic subscriber and a message selector
The section covers the following topics:
Writing the Application Components for the clientsessionmdb Example
Building, Deploying, and Running the clientsessionmdb Example Using NetBeans IDE
Building, Deploying, and Running the clientsessionmdb Example Using Ant
You will find the source files for this section in the directory tut-install/javaeetutorial5/examples/jms/clientsessionmdb/. Path names in this section are relative to this directory.
This application demonstrates how to send messages from an enterprise bean (in this case, a session bean) rather than from an application client, as in the example in Chapter 23, A Message-Driven Bean Example. Figure 32–1 illustrates the structure of this application.
The Publisher enterprise bean in this example is the enterprise-application equivalent of a wire-service news feed that categorizes news events into six news categories. The message-driven bean could represent a newsroom, where the sports desk, for example, would set up a subscription for all news events pertaining to sports.
The application client in the example injects the Publisher enterprise bean’s remote home interface and then calls the bean’s business method. The enterprise bean creates 18 text messages. For each message, it sets a String property randomly to one of six values representing the news categories and then publishes the message to a topic. The message-driven bean uses a message selector for the property to limit which of the published messages it receives.
Writing the components of the application involves the following:
The application client program, clientsessionmdb-app-client/src/java/MyAppClient.java, performs no JMS API operations and so is simpler than the client program in Chapter 23, A Message-Driven Bean Example. The program uses dependency injection to obtain the Publisher enterprise bean’s business interface:
@EJB(name="PublisherRemote") static private PublisherRemote publisher;
The program then calls the bean’s business method twice.
The Publisher bean is a stateless session bean that has one business method. The Publisher bean uses a remote interface rather than a local interface because it is accessed from the application client.
The remote interface, clientsessionmdb-ejb/src/java/sb/PublisherRemote.java, declares a single business method, publishNews.
The bean class, clientsessionmdb-ejb/src/java/sb/PublisherBean.java, implements the publishNews method and its helper method chooseType. The bean class also injects SessionContext, ConnectionFactory, and Topic resources and implements @PostConstruct and @PreDestroy callback methods. The bean class begins as follows:
@Stateless @Remote({PublisherRemote.class}) public class PublisherBean implements PublisherRemote { @Resource private SessionContext sc; @Resource(mappedName="jms/ConnectionFactory") private ConnectionFactory connectionFactory; @Resource(mappedName="jms/Topic") private Topic topic; ...
The @PostConstruct callback method of the bean class, makeConnection, creates the Connection used by the bean. The business method publishNews creates a Session and a MessageProducer and publishes the messages.
The @PreDestroy callback method, endConnection, deallocates the resources that were allocated by the @PostConstruct callback method. In this case, the method closes the Connection.
The message-driven bean class, clientsessionmdb-ejb/src/java/mdb/MessageBean.java, is almost identical to the one in Chapter 23, A Message-Driven Bean Example. However, the @MessageDriven annotation is different, because instead of a queue the bean is using a topic with a durable subscription, and it is also using a message selector. Therefore, the annotation sets the activation config properties messageSelector, subscriptionDurability, clientId, and subscriptionName, as follows:
@MessageDriven(mappedName="jms/Topic", activationConfig= { @ActivationConfigProperty(propertyName="messageSelector", propertyValue="NewsType = ’Sports’ OR NewsType = ’Opinion’"), @ActivationConfigProperty( propertyName="subscriptionDurability", propertyValue="Durable"), @ActivationConfigProperty(propertyName="clientId", propertyValue="MyID"), @ActivationConfigProperty(propertyName="subscriptionName", propertyValue="MySub") })
The JMS resource adapter uses these properties to create a connection factory for the message-driven bean that allows the bean to use a durable subscriber.
This example uses the topic named jms/Topic and the connection factory jms/ConnectionFactory, which you created in Creating JMS Administered Objects for the Synchronous Receive Example. If you deleted the connection factory or topic, you can create them again using targets in the build.xml file for this example. Use the following commands to create the resources:
ant create-cf ant create-topic |
To build, deploy, and run the application using NetBeans IDE, do the following:
Start the Application Server, if it is not already running.
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/.
Select the clientsessionmdb folder.
Select the Open as Main Project check box and the Open Required Projects check box.
Click Open Project.
Right-click the clientsessionmdb project and choose Build.
This task creates the following:
An application client JAR file that contains the client class file and the session bean’s remote interface, along with a manifest file that specifies the main class
An EJB JAR file that contains both the session bean and the message-driven bean
An application EAR file that contains the two JAR files
Right-click the project and choose Undeploy and Deploy.
Right-click the project and choose Run.
This command returns a JAR file named clientsessionmdbClient.jar and then executes it.
The output of the application client in the Output pane looks like this:
To view the bean output, check <install_dir>/domains/domain1/logs/server.log. |
The output from the enterprise beans appears in the server log (domain-dir/logs/server.log), wrapped in logging information. The Publisher session bean sends two sets of 18 messages numbered 0 through 17. Because of the message selector, the message-driven bean receives only the messages whose NewsType property is Sports or Opinion.
Undeploy the application after you finish running the client. To undeploy the application, follow these steps:
Click the Services tab.
Expand the Servers node.
Expand the Sun Java System Application Server node.
Expand the Applications node.
Expand the Enterprise Applications node.
Right-click clientsessionmdb and choose Undeploy.
To remove the generated files, right-click the clientsessionmdb project and choose Clean.
To build the application using Ant, do the following:
Start the Application Server, if it is not already running.
Go to the following directory:
tut-install/javaeetutorial5/examples/jms/clientsessionmdb/
To compile the source files and package the application, use the following command:
ant |
The ant command creates the following:
An application client JAR file that contains the client class file and the session bean’s remote interface, along with a manifest file that specifies the main class
An EJB JAR file that contains both the session bean and the message-driven bean
An application EAR file that contains the two JAR files
The clientsessionmdb.ear file is created in the clientsessionmdb/dist directory.
To deploy the application and run the client, use the following command:
ant run |
Ignore the message that states that the application is deployed at a URL.
The client displays these lines:
running application client container. To view the bean output, check <install_dir>/domains/domain1/logs/server.log.
The output from the enterprise beans appears in the server log (domain-dir/logs/server.log), wrapped in logging information. The Publisher session bean sends two sets of 18 messages numbered 0 through 17. Because of the message selector, the message-driven bean receives only the messages whose NewsType property is Sports or Opinion.
Undeploy the application after you finish running the client. Use the following command:
ant undeploy |
To remove the generated files, use the following command in the clientsessionmdb, clientsessionmdb-app-client, and clientsessionmdb-ejb directories:
ant clean |
This section explains how to write, compile, package, deploy, and run a Java EE application that uses the JMS API with an entity. The application uses the following components:
An application client that both sends and receives messages
Two message-driven beans
An entity class
This section covers the following topics:
Writing the Application Components for the clientmdbentity Example
Building, Deploying, and Running the clientmdbentity Example Using NetBeans IDE
Building, Deploying, and Running the clientmdbentity Example Using Ant
You will find the source files for this section in the directory tut-install/javaeetutorial5/examples/jms/clientmdbentity/. Path names in this section are relative to this directory.
This application simulates, in a simplified way, the work flow of a company’s human resources (HR) department when it processes a new hire. This application also demonstrates how to use the Java EE platform to accomplish a task that many JMS client applications perform.
A JMS client must often wait for several messages from various sources. It then uses the information in all these messages to assemble a message that it then sends to another destination. The common term for this process is joining messages. Such a task must be transactional, with all the receives and the send as a single transaction. If not all the messages are received successfully, the transaction can be rolled back. For a client example that illustrates this task, see A Local Transaction Example.
A message-driven bean can process only one message at a time in a transaction. To provide the ability to join messages, a Java EE application can have the message-driven bean store the interim information in an entity. The entity can then determine whether all the information has been received; when it has, the entity can report this back to one of the message-driven beans, which then creates and sends the message to the other destination. After it has completed its task, the entity can be removed.
The basic steps of the application are as follows.
The HR department’s application client generates an employee ID for each new hire and then publishes a message (M1) containing the new hire’s name, employee ID, and position. The client then creates a temporary queue, ReplyQueue, with a message listener that waits for a reply to the message. (See Creating Temporary Destinations, for more information.)
Two message-driven beans process each message: One bean, OfficeMDB, assigns the new hire’s office number, and the other bean, EquipmentMDB, assigns the new hire’s equipment. The first bean to process the message creates and persists an entity named SetupOffice, then calls a business method of the entity to store the information it has generated. The second bean locates the existing entity and calls another business method to add its information.
When both the office and the equipment have been assigned, the entity business method returns a value of true to the message-driven bean that called the method. The message-driven bean then sends to the reply queue a message (M2) describing the assignments. Then it removes the entity. The application client’s message listener retrieves the information.
Figure 32–2 illustrates the structure of this application. Of course, an actual HR application would have more components; other beans could set up payroll and benefits records, schedule orientation, and so on.
Figure 32–2 assumes that OfficeMDB is the first message-driven bean to consume the message from the client. OfficeMDB then creates and persists the SetupOffice entity and stores the office information. EquipmentMDB then finds the entity, stores the equipment information, and learns that the entity has completed its work. EquipmentMDB then sends the message to the reply queue and removes the entity.
Writing the components of the application involves the following:
The application client program, clientmdbentity-app-client/src/java/HumanResourceClient.java, performs the following steps:
Injects ConnectionFactory and Topic resources
Creates a TemporaryQueue to receive notification of processing that occurs, based on new-hire events it has published
Creates a MessageConsumer for the TemporaryQueue, sets the MessageConsumer’s message listener, and starts the connection
Creates a MessageProducer and a MapMessage
Creates five new employees with randomly generated names, positions, and ID numbers (in sequence) and publishes five messages containing this information
The message listener, HRListener, waits for messages that contain the assigned office and equipment for each employee. When a message arrives, the message listener displays the information received and determines whether all five messages have arrived. When they have, the message listener notifies the main program, which then exits.
This example uses two message-driven beans:
clientmdbentity-ejb/src/java/EquipmentMDB.java
clientmdbentity-ejb/src/java/OfficeMDB.java
The beans take the following steps:
They inject MessageDrivenContext and ConnectionFactory resources.
The onMessage method retrieves the information in the message. The EquipmentMDB’s onMessage method chooses equipment, based on the new hire’s position; the OfficeMDB’s onMessage method randomly generates an office number.
After a slight delay to simulate real world processing hitches, the onMessage method calls a helper method, compose.
The compose method takes the following steps:
It either creates and persists the SetupOffice entity or finds it by primary key.
It uses the entity to store the equipment or the office information in the database, calling either the doEquipmentList or the doOfficeNumber business method.
If the business method returns true, meaning that all of the information has been stored, it creates a connection and a session, retrieves the reply destination information from the message, creates a MessageProducer, and sends a reply message that contains the information stored in the entity.
It removes the entity.
The SetupOffice class, SetupOffice.java, is an entity class. The entity and the message-driven beans are packaged together in an EJB JAR file. The entity class is declared as follows:
@Entity public class SetupOffice implements Serializable {
The class contains a no-argument constructor and a constructor that takes two arguments, the employee ID and name. It also contains getter and setter methods for the employee ID, name, office number, and equipment list. The getter method for the employee ID has the @Id annotation to indicate that this field is the primary key:
@Id public String getEmployeeId() { return id; }
The class also implements the two business methods, doEquipmentList and doOfficeNumber, and their helper method, checkIfSetupComplete.
The message-driven beans call the business methods and the getter methods.
The persistence.xml file for the entity specifies the most basic settings:
<persistence> <persistence-unit name="clientmdbentity"> <jta-data-source>jdbc/__default</jta-data-source> <class>eb.SetupOffice</class> <properties> <property name="toplink.ddl-generation" value="drop-and-create-tables"/> </properties> </persistence-unit> </persistence>
This example uses the connection factory jms/ConnectionFactory and the topic jms/Topic, both of which you used in A Java EE Application That Uses the JMS API with a Session Bean. It also uses the JDBC resource named jdbc/__default, which is enabled by default when you start the Application Server.
If you deleted the connection factory or topic, you can create them again using targets in the build.xml file for this example. Use the following commands to create the resources:
ant create-cf ant create-topic |
To build, deploy, and run the application using NetBeans IDE, do the following:
Start the Application Server, if it is not already running.
Start the database server as described in Starting and Stopping the Java DB Database Server, if it is not already running.
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/.
Select the clientmdbentity folder.
Select the Open as Main Project check box and the Open Required Projects check box.
Click Open Project.
Right-click the clientmdbentity project and choose Build.
This task creates the following:
An application client JAR file that contains the client class and listener class files, along with a manifest file that specifies the main class
An EJB JAR file that contains the message-driven beans and the entity class, along with the persistence.xml file
An application EAR file that contains the two JAR files along with an application.xml file
Right-click the project and choose Undeploy and Deploy.
Right-click the project and choose Run.
This command returns a JAR file named clientmdbentityClient.jar and then executes it.
The output of the application client in the Output pane looks something like this:
PUBLISHER: Setting hire ID to 25, name Gertrude Bourbon, position Senior Programmer PUBLISHER: Setting hire ID to 26, name Jack Verdon, position Manager PUBLISHER: Setting hire ID to 27, name Fred Tudor, position Manager PUBLISHER: Setting hire ID to 28, name Fred Martin, position Programmer PUBLISHER: Setting hire ID to 29, name Mary Stuart, position Manager Waiting for 5 message(s) New hire event processed: Employee ID: 25 Name: Gertrude Bourbon Equipment: Laptop Office number: 183 Waiting for 4 message(s) New hire event processed: Employee ID: 26 Name: Jack Verdon Equipment: Pager Office number: 20 Waiting for 3 message(s) New hire event processed: Employee ID: 27 Name: Fred Tudor Equipment: Pager Office number: 51 Waiting for 2 message(s) New hire event processed: Employee ID: 28 Name: Fred Martin Equipment: Desktop System Office number: 141 Waiting for 1 message(s) New hire event processed: Employee ID: 29 Name: Mary Stuart Equipment: Pager Office number: 238 |
The output from the message-driven beans and the entity class appears in the server log, wrapped in logging information.
For each employee, the application first creates the entity and then finds it. You may see runtime errors in the server log, and transaction rollbacks may occur. The errors occur if both of the message-driven beans discover at the same time that the entity does not yet exist, so they both try to create it. The first attempt succeeds, but the second fails because the bean already exists. After the rollback, the second message-driven bean tries again and succeeds in finding the entity. Container-managed transactions allow the application to run correctly, in spite of these errors, with no special programming.
You can run the application client repeatedly.
Undeploy the application after you finish running the client. To undeploy the application, follow these steps:
Click the Services tab.
Expand the Servers node.
Expand the Sun Java System Application Server node.
Expand the Applications node.
Expand the Enterprise Applications node.
Right-click clientmdbentity and choose Undeploy.
To remove the generated files, right-click the clientmdbentity project and choose Clean.
To create and package the application using Ant, perform these steps:
Start the Application Server, if it is not already running.
Start the database server as described in Starting and Stopping the Java DB Database Server.
Go to the following directory:
tut-install/javaeetutorial5/examples/jms/clientmdbentity/
To compile the source files and package the application, use the following command:
ant |
The ant command creates the following:
An application client JAR file that contains the client class and listener class files, along with a manifest file that specifies the main class
An EJB JAR file that contains the message-driven beans and the entity class, along with the persistence.xml file
An application EAR file that contains the two JAR files along with an application.xml file
To deploy the application and run the client, use the following command:
ant run |
Ignore the message that states that the application is deployed at a URL.
The program output in the terminal window looks something like this:
running application client container. PUBLISHER: Setting hire ID to 25, name Gertrude Bourbon, position Senior Programmer PUBLISHER: Setting hire ID to 26, name Jack Verdon, position Manager PUBLISHER: Setting hire ID to 27, name Fred Tudor, position Manager PUBLISHER: Setting hire ID to 28, name Fred Martin, position Programmer PUBLISHER: Setting hire ID to 29, name Mary Stuart, position Manager Waiting for 5 message(s) New hire event processed: Employee ID: 25 Name: Gertrude Bourbon Equipment: Laptop Office number: 183 Waiting for 4 message(s) New hire event processed: Employee ID: 26 Name: Jack Verdon Equipment: Pager Office number: 20 Waiting for 3 message(s) New hire event processed: Employee ID: 27 Name: Fred Tudor Equipment: Pager Office number: 51 Waiting for 2 message(s) New hire event processed: Employee ID: 28 Name: Fred Martin Equipment: Desktop System Office number: 141 Waiting for 1 message(s) New hire event processed: Employee ID: 29 Name: Mary Stuart Equipment: Pager Office number: 238 |
The output from the message-driven beans and the entity class appears in the server log, wrapped in logging information.
For each employee, the application first creates the entity and then finds it. You may see runtime errors in the server log, and transaction rollbacks may occur. The errors occur if both of the message-driven beans discover at the same time that the entity does not yet exist, so they both try to create it. The first attempt succeeds, but the second fails because the bean already exists. After the rollback, the second message-driven bean tries again and succeeds in finding the entity. Container-managed transactions allow the application to run correctly, in spite of these errors, with no special programming.
Undeploy the application after you finish running the client:
ant undeploy |
To remove the generated files, use the following command in the clientmdbentity, clientmdbentity-app-client, and clientmdbentity-ejb directories:
ant clean |
This section and the following section explain how to write, compile, package, deploy, and run a pair of Java EE modules that run on two Java EE servers and that use the JMS API to interchange messages with each other. It is a common practice to deploy different components of an enterprise application on different systems within a company, and these examples illustrate on a small scale how to do this for an application that uses the JMS API.
However, the two examples work in slightly different ways. In this first example, the deployment information for a message-driven bean specifies the remote server from which it will consume messages. In the next example, the same bean is deployed on two different servers, so it is the client module that specifies the servers (one local, one remote) to which it is sending messages.
This first example divides the example in Chapter 23, A Message-Driven Bean Example into two modules (not applications): one containing the application client, and the other containing the message-driven bean.
This section covers the following topics:
Building, Deploying, and Running the consumeremoteModules Using NetBeans IDE
Building, Deploying, and Running the consumeremote Modules Using Ant
You will find the source files for this section in tut-install/javaeetutorial5/examples/jms/consumeremote/. Path names in this section are relative to this directory.
Except for the fact that it is packaged as two separate modules, this example is very similar to the one in Chapter 23, A Message-Driven Bean Example:
One module contains the application client, which runs on the remote system and sends three messages to a queue.
The other module contains the message-driven bean, which is deployed on the local server and consumes the messages from the queue on the remote server.
The basic steps of the modules are as follows.
The administrator starts two Java EE servers, one on each system.
On the local server, the administrator deploys the message-driven bean module, which uses a connection factory that specifies the remote server where the client is deployed.
On the remote server, the administrator places the client JAR file.
The client module sends three messages to a queue.
The message-driven bean consumes the messages.
Figure 32–3 illustrates the structure of this application. You can see that it is almost identical to Figure 23–1 except that there are two Java EE servers. The queue used is the one on the remote server; the queue must also exist on the local server for resource injection to succeed.
Writing the components of the modules involves
Coding the application client
Coding the message-driven bean
The application client, jupiterclient/src/java/SimpleClient.java, is almost identical to the one in The simplemessage Application Client.
Similarly, the message-driven bean, earthmdb/src/java/MessageBean.java, is almost identical to the one in The Message-Driven Bean Class.
The only major difference is that the client and the bean are packaged in two separate modules.
For this example, the message-driven bean uses the connection factory named jms/JupiterConnectionFactory, which you created in Creating Administered Objects for Multiple Systems. Use the Admin Console to verify that the connection factory still exists and that its AddressList property is set to the name of the remote system. Because this bean must use a specific connection factory, the connection factory is specified in the mdb-connection-factory element of the sun-ejb-jar.xml file.
If you deleted the connection factory, you can recreate it as follows:
Go to the following directory:
tut-install/javaeetutorial5/examples/jms/consumeremote/earthmdb/
Type the following command:
ant create-remote-factory -Dsys=remote-system-name |
Replace remote-system-name with the actual name of the remote system.
The application client can use any connection factory that exists on the remote server; it uses jms/ConnectionFactory. Both components use the queue named jms/Queue, which you created in Creating JMS Administered Objects for the Synchronous Receive Example.
As in Running JMS Client Programs on Multiple Systems, the two servers are named earth and jupiter.
The Application Server must be running on both systems.
Which system you use to package and deploy the modules and which system you use to run the client depend on your network configuration (which file system you can access remotely). These instructions assume that you can access the file system of jupiter from earth but cannot access the file system of earth from jupiter. (You can use the same systems for jupiter and earth that you used in Running JMS Client Programs on Multiple Systems.)
You can package both modules on earth and deploy the message-driven bean there. The only action you perform on jupiter is running the client module.
To package the modules using NetBeans IDE, perform these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/consumeremote/.
Select the earthmdb folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the earthmdb project and choose Build.
This command creates a JAR file that contains the bean class file and the sun-ejb-jar.xml deployment descriptor file.
Choose Open Project from the File menu.
Select the jupiterclient folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the jupiterclient project and choose Build.
This target creates a JAR file that contains the client class file and a manifest file.
To deploy the earthmdb module and run the application client, perform these steps:
Right-click the earthmdb project and choose Set as Main Project.
Right-click the earthmdb project and choose Undeploy and Deploy.
Copy the jupiterclient module to the remote system (jupiter):
In a terminal window, change to the directory tut-install/javaeetutorial5/examples/jms/consumeremote/jupiterclient/dist/.
Type a command like the following:
cp jupiterclient.jar F:/ |
That is, copy the client JAR file to a location on the remote filesystem.
Go to the directory on the remote system where you copied the client JAR file.
Use the following command:
appclient -client jupiterclient.jar |
On jupiter, the output of the appclient command looks like this:
Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
On earth, the output in the server log looks something like this (wrapped in logging information):
MESSAGE BEAN: Message received: This is message 1 MESSAGE BEAN: Message received: This is message 2 MESSAGE BEAN: Message received: This is message 3 |
Undeploy the message-driven bean after you finish running the client. To undeploy the earthmdb module, perform these steps:
Click the Services tab.
Expand the Servers node.
Expand the Sun Java System Application Server node.
Expand the Applications node.
Expand the EJB Modules node.
Right-click earthmdb and choose Undeploy.
To remove the generated files, follow these steps:
Right-click the earthmdb project and choose Clean.
In the command line window from which you copied the client JAR file, go to a directory other than the jupiterclient/dist directory.
Right-click the jupiterclient project and choose Clean.
You can also delete the jupiterclient.jar file from the remote filesystem.
To package the modules using Ant, perform these steps:
Go to the following directory:
tut-install/javaeetutorial5/examples/jms/consumeremote/earthmdb/
Type the following command:
ant |
This command creates a JAR file that contains the bean class file and the sun-ejb-jar.xml deployment descriptor file.
Go to the jupiterclient directory:
cd ../jupiterclient |
Type the following command:
ant |
This target creates a JAR file that contains the client class file and a manifest file.
To deploy the earthmdb module, perform these steps:
Change to the directory earthmdb:
cd ../earthmdb |
Type the following command:
ant deploy |
To copy the jupiterclient module to the remote system, perform these steps:
Change to the directory jupiterclient/dist:
cd ../jupiterclient/dist |
Type a command like the following:
cp jupiterclient.jar F:/ |
That is, copy the client JAR file to a location on the remote filesystem.
To run the client, perform the following steps:
Go to the directory on the remote system (jupiter) where you copied the client JAR file.
Use the following command:
appclient -client jupiterclient.jar |
On jupiter, the output of the appclient command looks like this:
Sending message: This is message 1 Sending message: This is message 2 Sending message: This is message 3 |
On earth, the output in the server log looks something like this (wrapped in logging information):
MESSAGE BEAN: Message received: This is message 1 MESSAGE BEAN: Message received: This is message 2 MESSAGE BEAN: Message received: This is message 3 |
Undeploy the message-driven bean after you finish running the client. To undeploy the earthmdb module, perform these steps:
Change to the directory earthmdb.
Type the following command:
ant undeploy |
You can also delete the jupiterclient.jar file from the remote filesystem.
To remove the generated files, use the following command in both the earthmdb and jupiterclient directories:
ant clean |
This section, like the preceding one, explains how to write, compile, package, deploy, and run a pair of Java EE modules that use the JMS API and run on two Java EE servers. The modules are slightly more complex than the ones in the first example.
The modules use the following components:
An application client that is deployed on the local server. It uses two connection factories, one ordinary one and one that is configured to communicate with the remote server, to create two publishers and two subscribers and to publish and to consume messages.
A message-driven bean that is deployed twice: once on the local server, and once on the remote one. It processes the messages and sends replies.
In this section, the term local server means the server on which both the application client and the message-driven bean are deployed (earth in the preceding example). The term remote server means the server on which only the message-driven bean is deployed (jupiter in the preceding example).
The section covers the following topics:
Building, Deploying, and Running the sendremote Modules Using NetBeans IDE
Building, Deploying, and Running the sendremote Modules Using Ant
You will find the source files for this section in tut-install/javaeetutorial5/examples/jms/sendremote/. Path names in this section are relative to this directory.
This pair of modules is somewhat similar to the modules in An Application Example That Consumes Messages from a Remote Server in that the only components are a client and a message-driven bean. However, the modules here use these components in more complex ways. One module consists of the application client. The other module contains only the message-driven bean and is deployed twice, once on each server.
The basic steps of the modules are as follows.
You start two Java EE servers, one on each system.
On the local server (earth), you create two connection factories: one local and one that communicates with the remote server (jupiter). On the remote server, you create a connection factory that has the same name.
The application client looks up the two connection factories (the local one and the one that communicates with the remote server) to create two connections, sessions, publishers, and subscribers. The subscribers use a message listener.
Each publisher publishes five messages.
Each of the local and the remote message-driven beans receives five messages and sends replies.
The client’s message listener consumes the replies.
Figure 32–4 illustrates the structure of this application. M1 represents the first message sent using the local connection factory, and RM1 represents the first reply message sent by the local MDB. M2 represents the first message sent using the remote connection factory, and RM2 represents the first reply message sent by the remote MDB.
Writing the components of the modules involves two tasks:
The application client class, multiclient/src/java/MultiAppServerClient.java, does the following.
It injects resources for two connection factories and a topic.
For each connection factory, it creates a connection, a publisher session, a publisher, a subscriber session, a subscriber, and a temporary topic for replies.
Each subscriber sets its message listener, ReplyListener, and starts the connection.
Each publisher publishes five messages and creates a list of the messages the listener should expect.
When each reply arrives, the message listener displays its contents and removes it from the list of expected messages.
When all the messages have arrived, the client exits.
The message-driven bean class, replybean/src/ReplyMsgBean.java, does the following:
Uses the @MessageDriven annotation:
@MessageDriven(mappedName="jms/Topic")
Injects resources for the MessageDrivenContext and for a connection factory. It does not need a destination resource because it uses the value of the incoming message’s JMSReplyTo header as the destination.
Uses a @PostConstruct callback method to create the connection, and a @PreDestroy callback method to close the connection.
The onMessage method of the message-driven bean class does the following:
Casts the incoming message to a TextMessage and displays the text
Creates a connection, a session, and a publisher for the reply message
Publishes the message to the reply topic
Closes the connection
On both servers, the bean will consume messages from the topic jms/Topic.
This example uses the connection factory named jms/ConnectionFactory and the topic named jms/Topic. These objects must exist on both the local and the remote servers.
This example uses an additional connection factory, jms/JupiterConnectionFactory, which communicates with the remote system; you created it in Creating Administered Objects for Multiple Systems. This connection factory must exist on the local server.
The build.xml file for the multiclient module contains targets that you can use to create these resources if you deleted them previously.
If you are using NetBeans IDE, you need to add the remote server in order to deploy the message-driven bean there. To do so, perform these steps:
In NetBeans IDE, click the Services tab.
Right-click the Servers node and choose Add Server. In the Add Server Instance dialog, perform these steps:
Select Sun Java System Application Server (the default) from the Server list.
In the Name field, specify a name slightly different from that of the local server, such as Sun Java System Application Server (1).
Click Next.
For the Platform Folder location, you can either browse to the location of the Application Server on the remote system or, if that location is not visible from the local system, use the default location on the local system.
Select the Register Remote Domain radio button.
Click Next.
Type the system name of the host in the Host field.
Click Next.
Type the administrative username and password for the remote system in the Admin Username and Admin Password fields.
Click Finish.
There may be a delay while NetBeans IDE registers the remote domain.
To package the modules using NetBeans IDE, perform these steps:
In NetBeans IDE, choose Open Project from the File menu.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/sendremote/.
Select the replybean folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the replybean project and choose Build.
This command creates a JAR file that contains the bean class file.
Choose Open Project from the File menu.
Select the multiclient folder.
Select the Open as Main Project check box.
Click Open Project.
Right-click the multiclient project and choose Build.
This command creates a JAR file that contains the client class file and a manifest file.
To deploy the multiclient module on the local server, perform these steps:
Right-click the multiclient project and choose Properties.
Select Run from the Categories tree.
From the Server list, select Sun Java System Application Server (the local server).
Click OK.
Right-click the multiclient project and choose Undeploy and Deploy.
To deploy the replybean module on the local and remote servers, perform these steps:
Right-click the replybean project and choose Properties.
Select Run from the Categories tree.
From the Server list, select Sun Java System Application Server(the local server).
Click OK.
Right-click the replybean project and choose Undeploy and Deploy.
Right-click the replybean project again and choose Properties.
Select Run from the Categories tree.
From the Server list, select Sun Java System Application Server (1) (the remote server).
Click OK.
Right-click the replybean project and choose Undeploy and Deploy.
You can use the Services tab to verify that multiclient is deployed as an App Client Module on the local server and that replybean is deployed as an EJB Module on both servers.
To run the application client, right-click the multiclient project and choose Run.
This command returns a JAR file named multiclientClient.jar and then executes it.
On the local system, the output of the appclient command looks something like this:
running application client container. Sent message: text: id=1 to local app server Sent message: text: id=2 to remote app server ReplyListener: Received message: id=1, text=ReplyMsgBean processed message: text: id=1 to local app server Sent message: text: id=3 to local app server ReplyListener: Received message: id=3, text=ReplyMsgBean processed message: text: id=3 to local app server ReplyListener: Received message: id=2, text=ReplyMsgBean processed message: text: id=2 to remote app server Sent message: text: id=4 to remote app server ReplyListener: Received message: id=4, text=ReplyMsgBean processed message: text: id=4 to remote app server Sent message: text: id=5 to local app server ReplyListener: Received message: id=5, text=ReplyMsgBean processed message: text: id=5 to local app server Sent message: text: id=6 to remote app server ReplyListener: Received message: id=6, text=ReplyMsgBean processed message: text: id=6 to remote app server Sent message: text: id=7 to local app server ReplyListener: Received message: id=7, text=ReplyMsgBean processed message: text: id=7 to local app server Sent message: text: id=8 to remote app server ReplyListener: Received message: id=8, text=ReplyMsgBean processed message: text: id=8 to remote app server Sent message: text: id=9 to local app server ReplyListener: Received message: id=9, text=ReplyMsgBean processed message: text: id=9 to local app server Sent message: text: id=10 to remote app server ReplyListener: Received message: id=10, text=ReplyMsgBean processed message: text: id=10 to remote app server Waiting for 0 message(s) from local app server Waiting for 0 message(s) from remote app server Finished Closing connection 1 Closing connection 2 |
On the local system, where the message-driven bean receives the odd-numbered messages, the output in the server log looks like this (wrapped in logging information):
ReplyMsgBean: Received message: text: id=1 to local app server ReplyMsgBean: Received message: text: id=3 to local app server ReplyMsgBean: Received message: text: id=5 to local app server ReplyMsgBean: Received message: text: id=7 to local app server ReplyMsgBean: Received message: text: id=9 to local app server |
On the remote system, where the bean receives the even-numbered messages, the output in the server log looks like this (wrapped in logging information):
ReplyMsgBean: Received message: text: id=2 to remote app server ReplyMsgBean: Received message: text: id=4 to remote app server ReplyMsgBean: Received message: text: id=6 to remote app server ReplyMsgBean: Received message: text: id=8 to remote app server ReplyMsgBean: Received message: text: id=10 to remote app server |
Undeploy the modules after you finish running the client. To undeploy the modules, perform these steps:
Click the Services tab.
Expand the Servers node.
Expand the Sun Java System Application Server node (the local system).
Expand the Applications node.
Expand the EJB Modules node.
Right-click replybean and choose Undeploy.
Expand the App Client Modules node.
Right-click multiclient and choose Undeploy.
Expand the Sun Java System Application Server (1) node (the remote system).
Expand the Applications node.
Expand the EJB Modules node.
Right-click replybean and choose Undeploy.
To remove the generated files, follow these steps:
Right-click the replybean project and choose Clean.
Right-click the multiclient project and choose Clean.
To package the modules, perform these steps:
Go to the following directory:
tut-install/javaeetutorial5/examples/jms/sendremote/multiclient/
Type the following command:
ant |
This command creates a JAR file that contains the client class file and a manifest file.
Change to the directory replybean:
cd ../replybean |
Type the following command:
ant |
This command creates a JAR file that contains the bean class file.
To deploy the replybean module on the local and remote servers, perform the following steps:
Verify that you are still in the directory replybean.
Type the following command:
ant deploy |
Ignore the message that states that the application is deployed at a URL.
Type the following command:
ant deploy-remote -Dsys=remote-system-name |
Replace remote-system-name with the actual name of the remote system.
To deploy and run the client, perform these steps:
Change to the directory multiclient:
cd ../multiclient |
Type the following command:
ant run |
On the local system, the output looks something like this:
running application client container. Sent message: text: id=1 to local app server Sent message: text: id=2 to remote app server ReplyListener: Received message: id=1, text=ReplyMsgBean processed message: text: id=1 to local app server Sent message: text: id=3 to local app server ReplyListener: Received message: id=3, text=ReplyMsgBean processed message: text: id=3 to local app server ReplyListener: Received message: id=2, text=ReplyMsgBean processed message: text: id=2 to remote app server Sent message: text: id=4 to remote app server ReplyListener: Received message: id=4, text=ReplyMsgBean processed message: text: id=4 to remote app server Sent message: text: id=5 to local app server ReplyListener: Received message: id=5, text=ReplyMsgBean processed message: text: id=5 to local app server Sent message: text: id=6 to remote app server ReplyListener: Received message: id=6, text=ReplyMsgBean processed message: text: id=6 to remote app server Sent message: text: id=7 to local app server ReplyListener: Received message: id=7, text=ReplyMsgBean processed message: text: id=7 to local app server Sent message: text: id=8 to remote app server ReplyListener: Received message: id=8, text=ReplyMsgBean processed message: text: id=8 to remote app server Sent message: text: id=9 to local app server ReplyListener: Received message: id=9, text=ReplyMsgBean processed message: text: id=9 to local app server Sent message: text: id=10 to remote app server ReplyListener: Received message: id=10, text=ReplyMsgBean processed message: text: id=10 to remote app server Waiting for 0 message(s) from local app server Waiting for 0 message(s) from remote app server Finished Closing connection 1 Closing connection 2 |
On the local system, where the message-driven bean receives the odd-numbered messages, the output in the server log looks like this (wrapped in logging information):
ReplyMsgBean: Received message: text: id=1 to local app server ReplyMsgBean: Received message: text: id=3 to local app server ReplyMsgBean: Received message: text: id=5 to local app server ReplyMsgBean: Received message: text: id=7 to local app server ReplyMsgBean: Received message: text: id=9 to local app server |
On the remote system, where the bean receives the even-numbered messages, the output in the server log looks like this (wrapped in logging information):
ReplyMsgBean: Received message: text: id=2 to remote app server ReplyMsgBean: Received message: text: id=4 to remote app server ReplyMsgBean: Received message: text: id=6 to remote app server ReplyMsgBean: Received message: text: id=8 to remote app server ReplyMsgBean: Received message: text: id=10 to remote app server |
Undeploy the modules after you finish running the client. To undeploy the multiclient module, perform these steps:
Verify that you are still in the directory multiclient.
Type the following command:
ant undeploy |
To undeploy the replybean module, perform these steps:
Change to the directory replybean:
cd ../replybean |
Type the following command:
ant undeploy |
Type the following command:
ant undeploy-remote -Dsys=remote-system-name |
Replace remote-system-name with the actual name of the remote system.
To remove the generated files, use the following command in both the replybean and multiclient directories:
ant clean |
A typical enterprise application accesses and stores information in one or more databases. Because this information is critical for business operations, it must be accurate, current, and reliable. Data integrity would be lost if multiple programs were allowed to update the same information simultaneously. It would also be lost if a system that failed while processing a business transaction were to leave the affected data only partially updated. By preventing both of these scenarios, software transactions ensure data integrity. Transactions control the concurrent access of data by multiple programs. In the event of a system failure, transactions make sure that after recovery the data will be in a consistent state.
To emulate a business transaction, a program may need to perform several steps. A financial program, for example, might transfer funds from a checking account to a savings account using the steps listed in the following pseudocode:
begin transaction debit checking account credit savings account update history log commit transaction
Either all three of these steps must complete, or none of them at all. Otherwise, data integrity is lost. Because the steps within a transaction are a unified whole, a transaction is often defined as an indivisible unit of work.
A transaction can end in two ways: with a commit or with a rollback. When a transaction commits, the data modifications made by its statements are saved. If a statement within a transaction fails, the transaction rolls back, undoing the effects of all statements in the transaction. In the pseudocode, for example, if a disk drive were to crash during the credit step, the transaction would roll back and undo the data modifications made by the debit statement. Although the transaction fails, data integrity would be intact because the accounts still balance.
In the preceding pseudocode, the begin and commit statements mark the boundaries of the transaction. When designing an enterprise bean, you determine how the boundaries are set by specifying either container-managed or bean-managed transactions.
In an enterprise bean with container-managed transaction demarcation, the EJB container sets the boundaries of the transactions. You can use container-managed transactions with any type of enterprise bean: session, or message-driven. Container-managed transactions simplify development because the enterprise bean code does not explicitly mark the transaction’s boundaries. The code does not include statements that begin and end the transaction.
By default if no transaction demarcation is specified enterprise beans use container-managed transaction demarcation.
Typically, the container begins a transaction immediately before an enterprise bean method starts. It commits the transaction just before the method exits. Each method can be associated with a single transaction. Nested or multiple transactions are not allowed within a method.
Container-managed transactions do not require all methods to be associated with transactions. When developing a bean, you can specify which of the bean’s methods are associated with transactions by setting the transaction attributes.
Enterprise beans that use container-managed transaction demarcation must not use any transaction management methods that interfere with the container’s transaction demarcation boundaries. Examples of such methods are the commit, setAutoCommit, and rollback methods of java.sql.Connection or the commit and rollback methods of javax.jms.Session. If you require control over the transaction demarcation, you must use application-managed transaction demarcation.
Enterprise beans that use container-managed transaction demarcation also must not use the javax.transaction.UserTransaction interface.
A transaction attribute controls the scope of a transaction. Figure 33–1 illustrates why controlling the scope is important. In the diagram, method-A begins a transaction and then invokes method-B of Bean-2. When method-B executes, does it run within the scope of the transaction started by method-A, or does it execute with a new transaction? The answer depends on the transaction attribute of method-B.
A transaction attribute can have one of the following values:
Required
RequiresNew
Mandatory
NotSupported
Supports
Never
If the client is running within a transaction and invokes the enterprise bean’s method, the method executes within the client’s transaction. If the client is not associated with a transaction, the container starts a new transaction before running the method.
The Required attribute is the implicit transaction attribute for all enterprise bean methods running with container-managed transaction demarcation. You typically do not set the Required attribute unless you need to override another transaction attribute. Because transaction attributes are declarative, you can easily change them later.
If the client is running within a transaction and invokes the enterprise bean’s method, the container takes the following steps:
Suspends the client’s transaction
Starts a new transaction
Delegates the call to the method
Resumes the client’s transaction after the method completes
If the client is not associated with a transaction, the container starts a new transaction before running the method.
You should use the RequiresNew attribute when you want to ensure that the method always runs within a new transaction.
If the client is running within a transaction and invokes the enterprise bean’s method, the method executes within the client’s transaction. If the client is not associated with a transaction, the container throws the TransactionRequiredException.
Use the Mandatory attribute if the enterprise bean’s method must use the transaction of the client.
If the client is running within a transaction and invokes the enterprise bean’s method, the container suspends the client’s transaction before invoking the method. After the method has completed, the container resumes the client’s transaction.
If the client is not associated with a transaction, the container does not start a new transaction before running the method.
Use the NotSupported attribute for methods that don’t need transactions. Because transactions involve overhead, this attribute may improve performance.
If the client is running within a transaction and invokes the enterprise bean’s method, the method executes within the client’s transaction. If the client is not associated with a transaction, the container does not start a new transaction before running the method.
Because the transactional behavior of the method may vary, you should use the Supports attribute with caution.
If the client is running within a transaction and invokes the enterprise bean’s method, the container throws a RemoteException. If the client is not associated with a transaction, the container does not start a new transaction before running the method.
Table 33–1 summarizes the effects of the transaction attributes. Both the T1 and the T2 transactions are controlled by the container. A T1 transaction is associated with the client that calls a method in the enterprise bean. In most cases, the client is another enterprise bean. A T2 transaction is started by the container just before the method executes.
In the last column of Table 33–1, the word None means that the business method does not execute within a transaction controlled by the container. However, the database calls in such a business method might be controlled by the transaction manager of the DBMS.
Table 33–1 Transaction Attributes and Scope
Transaction Attribute |
Client’s Transaction |
Business Method’s Transaction |
---|---|---|
Required |
None |
T2 |
T1 |
T1 |
|
RequiresNew |
None |
T2 |
T1 |
T2 |
|
Mandatory |
None |
error |
T1 |
T1 |
|
NotSupported |
None |
None |
T1 |
None |
|
Supports |
None |
None |
T1 |
T1 |
|
Never |
None |
None |
T1 |
Error |
Transaction attributes are specified by decorating the enterprise bean class or method with a javax.ejb.TransactionAttribute annotation, and setting it to one of the javax.ejb.TransactionAttributeType constants.
If you decorate the enterprise bean class with @TransactionAttribute, the specified TransactionAttributeType is applied to all the business methods in the class. Decoration a business method with @TransactionAttribute applies the TransactionAttributeType only to that method. If a @TransactionAttributeannotation decorates both the class and the method, the method TransactionAttributeType overrides the class TransactionAttributeType.
The TransactionAttributeType constants encapsulate the transaction attributes described earlier in this section.
Required: TransactionAttributeType.REQUIRED
RequiresNew: TransactionAttributeType.REQUIRES_NEW
Mandatory: TransactionAttributeType.MANDATORY
NotSupported: TransactionAttributeType.NOT_SUPPORTED
Supports: TransactionAttributeType.SUPPORTS
Never: TransactionAttributeType.NEVER
The following code snippet demonstrates how to use the @TransactionAttribute annotation:
@TransactionAttribute(NOT_SUPPORTED) @Stateful public class TransactionBean implements Transaction { ... @TransactionAttribute(REQUIRES_NEW) public void firstMethod() {...} @TransactionAttribute(REQUIRED) public void secondMethod() {...} public void thirdMethod() {...} public void fourthMethod() {...} }
In this example, the TransactionBean class’s transaction attribute has been set to NotSupported. firstMethod has been set to RequiresNew, and secondMethod has been set to Required. Because a @TransactionAttribute set on a method overrides the class @TransactionAttribute, calls to firstMethod will create a new transaction, and calls to secondMethod will either run in the current transaction, or start a new transaction. Calls to thirdMethod or fourthMethod do not take place within a transaction.
There are two ways to roll back a container-managed transaction. First, if a system exception is thrown, the container will automatically roll back the transaction. Second, by invoking the setRollbackOnly method of the EJBContext interface, the bean method instructs the container to roll back the transaction. If the bean throws an application exception, the rollback is not automatic but can be initiated by a call to setRollbackOnly.
The SessionSynchronization interface, which is optional, allows stateful session bean instances to receive transaction synchronization notifications. For example, you could synchronize the instance variables of an enterprise bean with their corresponding values in the database. The container invokes the SessionSynchronization methods (afterBegin, beforeCompletion, and afterCompletion) at each of the main stages of a transaction.
The afterBegin method informs the instance that a new transaction has begun. The container invokes afterBegin immediately before it invokes the business method.
The container invokes the beforeCompletion method after the business method has finished, but just before the transaction commits. The beforeCompletion method is the last opportunity for the session bean to roll back the transaction (by calling setRollbackOnly).
The afterCompletion method indicates that the transaction has completed. It has a single boolean parameter whose value is true if the transaction was committed and false if it was rolled back.
You should not invoke any method that might interfere with the transaction boundaries set by the container. The list of prohibited methods follows:
You can, however, use these methods to set boundaries in application-managed transactions.
In bean-managed transaction demarcation, the code in the session or message-driven bean explicitly marks the boundaries of the transaction. Although beans with container-managed transactions require less coding, they have one limitation: When a method is executing, it can be associated with either a single transaction or no transaction at all. If this limitation will make coding your bean difficult, you should consider using bean-managed transactions.
The following pseudocode illustrates the kind of fine-grained control you can obtain with application-managed transactions. By checking various conditions, the pseudocode decides whether to start or stop different transactions within the business method.
begin transaction ... update table-a ... if (condition-x) commit transaction else if (condition-y) update table-b commit transaction else rollback transaction begin transaction update table-c commit transaction
When coding a application-managed transaction for session or message-driven beans, you must decide whether to use JDBC or JTA transactions. The sections that follow discuss both types of transactions.
JTA is the abbreviation for the Java Transaction API. This API allows you to demarcate transactions in a manner that is independent of the transaction manager implementation. The Application Server implements the transaction manager with the Java Transaction Service (JTS). But your code doesn’t call the JTS methods directly. Instead, it invokes the JTA methods, which then call the lower-level JTS routines.
A JTA transaction is controlled by the Java EE transaction manager. You may want to use a JTA transaction because it can span updates to multiple databases from different vendors. A particular DBMS’s transaction manager may not work with heterogeneous databases. However, the Java EE transaction manager does have one limitation: it does not support nested transactions. In other words, it cannot start a transaction for an instance until the preceding transaction has ended.
To demarcate a JTA transaction, you invoke the begin, commit, and rollback methods of the javax.transaction.UserTransaction interface.
In a stateless session bean with bean-managed transactions, a business method must commit or roll back a transaction before returning. However, a stateful session bean does not have this restriction.
In a stateful session bean with a JTA transaction, the association between the bean instance and the transaction is retained across multiple client calls. Even if each business method called by the client opens and closes the database connection, the association is retained until the instance completes the transaction.
In a stateful session bean with a JDBC transaction, the JDBC connection retains the association between the bean instance and the transaction across multiple calls. If the connection is closed, the association is not retained.
Do not invoke the getRollbackOnly and setRollbackOnly methods of the EJBContext interface in bean-managed transactions. These methods should be used only in container-managed transactions. For bean-managed transactions, invoke the getStatus and rollback methods of the UserTransaction interface.
For container-managed transactions, you can use the Admin Console to configure the transaction timeout interval. SeeStarting the Admin Console.
In the Admin Console, expand the Configuration node and select Transaction Service.
On the Transaction Service page, set the value of the Transaction Timeout field to the value of your choice (for example, 5).
With this setting, if the transaction has not completed within 5 seconds, the EJB container rolls it back.
The default value is 0, meaning that the transaction will not time out.
Click Save.
For enterprise beans with bean-managed JTA transactions, you invoke the setTransactionTimeout method of the UserTransaction interface.
The Java EE transaction manager controls all enterprise bean transactions except for bean-managed JDBC transactions. The Java EE transaction manager allows an enterprise bean to update multiple databases within a transaction. The figures that follow show two scenarios for updating multiple databases in a single transaction.
In Figure 33–2, the client invokes a business method in Bean-A. The business method begins a transaction, updates Database X, updates Database Y, and invokes a business method in Bean-B. The second business method updates Database Z and returns control to the business method in Bean-A, which commits the transaction. All three database updates occur in the same transaction.
In Figure 33–3, the client calls a business method in Bean-A, which begins a transaction and updates Database X. Then Bean-A invokes a method in Bean-B, which resides in a remote Java EE server. The method in Bean-B updates Database Y. The transaction managers of the Java EE servers ensure that both databases are updated in the same transaction.
You can demarcate a transaction in a web component by using either the java.sql.Connection or javax.transaction.UserTransaction interface. These are the same interfaces that a session bean with bean-managed transactions can use. Transactions demarcated with the UserTransaction interface are discussed in the section JTA Transactions. For an example of a web component using transactions, see Accessing Databases.
Java EE components can access a wide variety of resources, including databases, mail sessions, Java Message Service objects, and URLs. The Java EE 5 platform provides mechanisms that allow you to access all these resources in a similar manner. This chapter describes how to get connections to several types of resources.
In a distributed application, components need to access other components and resources such as databases. For example, a servlet might invoke remote methods on an enterprise bean that retrieves information from a database. In the Java EE platform, the Java Naming and Directory Interface (JNDI) naming service enables components to locate other components and resources.
A resource is a program object that provides connections to systems, such as database servers and messaging systems. (A JDBC resource is sometimes referred to as a data source.) Each resource object is identified by a unique, people-friendly name, called the JNDI name.
For example, the JNDI name of the JDBC resource for the Java DB database that is shipped with the Application Server is jdbc/__default.
An administrator creates resources in a JNDI namespace. In the Application Server, you can use either the Admin Console or the asadmin command to create resources. Applications then use annotations to inject the resources. If an application uses resource injection, the Application Server invokes the JNDI API, and the application is not required to do so. However, it is also possible for an application to locate resources by making direct calls to the JNDI API.
A resource object and its JNDI name are bound together by the naming and directory service. To create a new resource, a new name-object binding is entered into the JNDI namespace.
For information on creating Java Message Service (JMS) resources, see Creating JMS Administered Objects for the Synchronous Receive Example. For an example of creating a JDBC resource, see Creating a Data Source in the Application Server.
You inject resources by using the @Resource annotation in an application. For information on resource injection, see the following sections of this Tutorial:
Updating Data in the Database, for information on injecting a UserTransaction resource
JMS Connection Factories, JMS Destinations, and Using @Resource Annotations in Java EE Components, for information on injecting JMS resources
You can use a deployment descriptor to override the resource mapping that you specify in an annotation. Using a deployment descriptor allows you to change an application by repackaging it, rather than by both recompiling the source files and repackaging. However, for most applications, a deployment descriptor is not necessary.
To store, organize, and retrieve data, most applications use a relational database. Java EE 5 components may access relational databases through the JDBC API. For information on this API, see http://java.sun.com/products/jdbc.
In the JDBC API, databases are accessed by using DataSource objects. A DataSource has a set of properties that identify and describe the real world data source that it represents. These properties include information such as the location of the database server, the name of the database, the network protocol to use to communicate with the server, and so on. In the Application Server, a data source is called a JDBC resource.
Applications access a data source using a connection, and a DataSource object can be thought of as a factory for connections to the particular data source that the DataSource instance represents. In a basic DataSource implementation, a call to the getConnection method returns a connection object that is a physical connection to the data source.
If a DataSource object is registered with a JNDI naming service, an application can use the JNDI API to access that DataSource object, which can then be used to connect to the data source it represents.
DataSource objects that implement connection pooling also produce a connection to the particular data source that the DataSource class represents. The connection object that the getConnection method returns is a handle to a PooledConnection object rather than being a physical connection. An application uses the connection object in the same way that it uses a connection. Connection pooling has no effect on application code except that a pooled connection, like all connections, should always be explicitly closed. When an application closes a connection that is pooled, the connection is returned to a pool of reusable connections. The next time getConnection is called, a handle to one of these pooled connections will be returned if one is available. Because connection pooling avoids creating a new physical connection every time one is requested, applications can run significantly faster.
A JDBC connection pool is a group of reusable connections for a particular database. Because creating each new physical connection is time consuming, the server maintains a pool of available connections to increase performance. When an application requests a connection, it obtains one from the pool. When an application closes a connection, the connection is returned to the pool.
Applications that use the Persistence API specify the DataSource object they are using in the jta-data-source element of the persistence.xml file.
<jta-data-source>jdbc/MyOrderDB</jta-data-source>
This is typically the only reference to a JDBC object for a persistence unit. The application code does not refer to any JDBC objects. For more details, see Persistence Units.
The javax.annotation.Resource annotation is used to declare a reference to a resource. @Resource can decorate a class, a field, or a method. The container will inject the resource referred to by @Resource into the component either at runtime or when the component is initialized, depending on whether field/method injection or class injection is used. With field and method-based injection, the container will inject the resource when the application is initialized. For class-based injection, the resource is looked up by the application at runtime.
@Resource has the following elements:
name: The JNDI name of the resource
type: The Java language type of the resource
authenticationType: The authentication type to use for the resource
shareable: Indicates whether the resource can be shared
mappedName: A non-portable, implementation-specific name to which the resource should be mapped
description: The description of the resource
The name element is the JNDI name of the resource, and is optional for field- and method-based injection. For field-based injection, the default name is the field name qualified by the class name. For method-based injection, the default name is the JavaBeans property name based on the method qualified by the class name. The name element must be specified for class-based injection.
The type of resource is determined by one of the following:
The type of the field the @Resource annotation is decorating for field-based injection
The type of the JavaBeans property the @Resource annotation is decorating for method-based injection
The type element of @Resource
For class-based injection, the type element is required.
The authenticationType element is used only for connection factory resources, and can be set to one of the javax.annotation.Resource.AuthenticationType enumerated type values: CONTAINER, the default, and APPLICATION.
The shareable element is used only for ORB instance resources or connection factory resource. It indicates whether the resource can be shared between this component and other components, and may be set to true, the default, or false.
The mappedName element is a non-portable, implementation-specific name that the resource should be mapped to. Because the name element, when specified or defaulted, is local only to the application, many Java EE servers provide a way of referring to resources across the application server. This is done by setting the mappedName element. Use of the mappedName element is non-portable across Java EE server implementations.
The description element is the description of the resource, typically in the default language of the system on which the application is deployed. It is used to help identify resources, and to help application developers choose the correct resource.
To use field-based resource injection, declare a field and decorate it with the @Resource annotation. The container will infer the name and type of the resource if the name and type elements are not specified. If you do specify the type element, it must match the field’s type declaration.
package com.example; public class SomeClass { @Resource private javax.sql.DataSource myDB; ... }
In the code above, the container infers the name of the resource based on the class name and the field name: com.example.SomeClass/myDB. The inferred type is javax.sql.DataSource.class.
package com.example; public class SomeClass { @Resource(name="customerDB") private javax.sql.DataSource myDB; ... }
In the code above, the JNDI name is customerDB, and the inferred type is javax.sql.DataSource.class.
To use method-based injection, declare a setter method and decorate it with the @Resource annotation. The container will infer the name and type of the resource if the name and type elements are not specified. The setter method must follow the JavaBeans conventions for property names: the method name must begin with set, have a void return type, and only one parameter. If you do specify the type element, it must match the field’s type declaration.
package com.example; public class SomeClass { private javax.sql.DataSource myDB; ... @Resource private void setMyDB(javax.sql.DataSource ds) { myDB = ds; } ... }
In the code above, the container infers the name of the resource based on the class name and the field name: com.example.SomeClass/myDB. The inferred type is javax.sql.DataSource.class.
package com.example; public class SomeClass { private javax.sql.DataSource myDB; ... @Resource(name="customerDB") private void setMyDB(javax.sql.DataSource ds) { myDB = ds; } ... }
In the code above, the JNDI name is customerDB, and the inferred type is javax.sql.DataSource.class.
To use class-based injection, decorate the class with a @Resource annotation, and set the required name and type elements.
@Resource(name="myMessageQueue", type="javax.jms.ConnectionFactory") public class SomeMessageBean { ... }
The @Resources annotation is used to group together multiple @Resource declarations for class-based injection.
@Resources({ @Resource(name="myMessageQueue", type="javax.jms.ConnectionFactory"), @Resource(name="myMailSession", type="javax.mail.Session") }) public class SomeMessageBean { ... }
The code above shows the @Resources annotation containing two @Resource declarations. One is a JMS message queue, and the other is a JavaMail session.
The confirmer example application demonstrates how to use an injected JavaMail session to send a confirmation email.
If you’ve ever ordered a product from a web site, you’ve probably received an email confirming your order. The ConfirmerBean class demonstrates how to send email from an enterprise bean.
Like a database connection, a mail session is a resource. In the Application Server, a mail session is called a JavaMail resource. The resource is injected into the class using @Resource and specifying the JNDI name of the resource. The type of the session field is javax.mail.Session.
@Resource(name="mail/myMailSession") private Session session;
After calling several set methods on the Message object, sendNotice invokes the send method of the javax.mail.Transport class to send the message. The source code for the sendNotice method follows.
public void sendNotice(String recipient) { try { Message message = new MimeMessage(session); message.setFrom(); message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient, false)); message.setSubject("Test Message from ConfirmerBean"); DateFormat dateFormatter = DateFormat .getDateTimeInstance(DateFormat.LONG, DateFormat.SHORT); Date timeStamp = new Date(); String messageText = "Thank you for your order." + ’\n’ + "We received your order on " + dateFormatter.format(timeStamp) + "."; message.setText(messageText); message.setHeader("X-Mailer", mailer); message.setSentDate(timeStamp); // Send message Transport.send(message); logger.info("Mail sent to " + recipient + "."); } catch (MessagingException ex) { ex.printStackTrace(); logger.info("Error in ConfirmerBean for " + recipient); } }
To run the confirmer example, follow these steps, as described in the following sections:
Create a mail session in the Admin Console.
Build the example.
Deploy the example.
Retrieve the client JAR.
Run the client JAR.
To create a mail session in the Application Server using the Admin Console, follow these steps:Open the URL http://localhost:4848/asadmin in a browser.
Select the JavaMail Sessions node.
Click New.
Type mail/myMailSession in the JNDI Name field.
Type the name of the host running your mail server in the Mail Host field.
Type the destination email address in the Default User field.
Type your email address in the Default Return Address field.
Click OK.
Note that mail/myMailSession is listed under the JavaMail Sessions node.
Follow these instructions to build, package, and deploy the confirmer example to your Application Server instance using the NetBeans IDE IDE.
In NetBeans IDE, select File->Open Project.
In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/ejb/.
Select the confirmer folder.
Select the Open as Main Project and Open Required Projects check boxes.
Click Open Project.
In the Projects tab, right-click the confirmer project and select Undeploy and Deploy.
This builds and packages the application into confirmer.ear, located in tut-install/javaeetutorial5/examples/ejb/confirmer/dist, and deploys this EAR file to your Application Server instance.
To build and package the confirmer example, do the following:
In a terminal window, go to tut-install/examples/ejb/confirmer.
Enter the following command:
ant |
This compiles the source code and creates an EAR file, confirmer.ear, in the dist directory.
To deploy confirmer.ear, type the following command in a terminal window:
ant deploy |
By default, the client sends a message to pig.bodine@example.com, a fictional email address. To change the email address in NetBeans IDE, do the following:
Right-click the confirmer project in the Projects pane and select Properties.
Click the Run category.
In the Client Information area, under Arguments, enter the email address to which you want the message sent.
Click OK.
To run the client in NetBeans IDE, right-click the confirmer project in the Projects pane and select Run. You should see the following line when the client has successfully sent the test message:
... Message sent to pig.bodine@example.com. ... |
By default, the client sends a message to pig.bodine@example.com, a fictional email address. To change the email address, set the app-client.args property in tut-install/examples/ejb/confirmer/nbproject/project.properties to the email address to which you’d like the test message sent. For example:
app-client.args=duke@example.com |
To retrieve the client JAR and run the client, enter the following command in a terminal:
ant run |
You should see the following line when the client has successfully sent the test message:
[exec] Message sent to pig.bodine@example.com. |
If you changed the target email address, the test message should arrive in the user’s inbox in a few moments.
For more information about resources and annotations, see:
Common Annotations for the Java Platform (JSR 250):
The Java EE 5 Platform Specification (JSR 244):
The Enterprise JavaBeans (EJB) 3.0 specification (JSR 220):
The Connector architecture enables Java EE components to interact with enterprise information systems (EISs) and EISs to interact with Java EE components. EIS software includes various types of systems: enterprise resource planning (ERP), mainframe transaction processing, and nonrelational databases, among others. Connector architecture simplifies the integration of diverse EISs. Each EIS requires only one implementation of the Connector architecture. Because an implementation adheres to the Connector specification, it is portable across all compliant Java EE servers.
A resource adapter is a Java EE component that implements the Connector architecture for a specific EIS. As illustrated in Figure 35–1, the resource adapter facilitates communication between a Java EE application and an EIS.
Stored in a Resource Adapter Archive (RAR) file, a resource adapter can be deployed on any Java EE server, much like the EAR file of a Java EE application. An RAR file may be contained in an EAR file, or it may exist as a separate file. See Figure 35–2 for the structure of a resource adapter module.
A resource adapter is analogous to a JDBC driver. Both provide a standard API through which an application can access a resource that is outside the Java EE server. For a resource adapter, the outside resource is an EIS; for a JDBC driver, it is a DBMS. Resource adapters and JDBC drivers are rarely created by application developers. In most cases, both types of software are built by vendors that sell products such as tools, servers, or integration software.
The resource adapter mediates communication between the Java EE server and the EIS by means of contracts. The application contract defines the API through which a Java EE component such as an enterprise bean accesses the EIS. This API is the only view that the component has of the EIS. The system contracts link the resource adapter to important services that are managed by the Java EE server. The resource adapter itself and its system contracts are transparent to the Java EE component.
The J2EE Connector architecture defines system contracts that enable resource adapter life cycle and thread management.
The Connector architecture specifies a life-cycle management contract that allows an application server to manage the life cycle of a resource adapter. This contract provides a mechanism for the application server to bootstrap a resource adapter instance during the instance’s deployment or application server startup. It also provides a means for the application server to notify the resource adapter instance when it is undeployed or when an orderly shutdown of the application server takes place.
The Connector architecture work management contract ensures that resource adapters use threads in the proper, recommended manner. It also enables an application server to manage threads for resource adapters.
Resource adapters that improperly use threads can create problems for the entire application server environment. For example, a resource adapter might create too many threads or it might not properly release threads it has created. Poor thread handling inhibits application server shutdown. It also impacts the application server’s performance because creating and destroying threads are expensive operations.
The work management contract establishes a means for the application server to pool and reuse threads, similar to pooling and reusing connections. By adhering to this contract, the resource adapter does not have to manage threads itself. Instead, the resource adapter has the application server create and provide needed threads. When the resource adapter is finished with a given thread, it returns the thread to the application server. The application server manages the thread: It can return the thread to a pool and reuse it later, or it can destroy the thread. Handling threads in this manner results in increased application server performance and more efficient use of resources.
In addition to moving thread management to the application server, the Connector architecture provides a flexible model for a resource adapter that uses threads:
The requesting thread can choose to block (stop its own execution) until the work thread completes.
Or the requesting thread can block while it waits to get the thread. When the application server provides a work thread, the requesting thread and the work thread execute in parallel.
The resource adapter can opt to submit the work for the thread to a queue. The thread executes the work from the queue at some later point. The resource adapter continues its own execution from the point it submitted the work to the queue, no matter of when the thread executes it.
With the latter two approaches, the resource adapter and the thread may execute simultaneously or independently from each other. For these approaches, the contract specifies a listener mechanism to notify the resource adapter that the thread has completed its operation. The resource adapter can also specify the execution context for the thread, and the work management contract controls the context in which the thread executes.
The Connector architecture defines system-level contracts between an application server and an EIS that enable outbound connectivity to an EIS: connection management, transaction management, and security.
The connection management contract supports connection pooling, a technique that enhances application performance and scalability. Connection pooling is transparent to the application, which simply obtains a connection to the EIS.
The transaction management contract between the transaction manager and an EIS supports transactional access to EIS resource managers. This contract lets an application server use a transaction manager to manage transactions across multiple resource managers. This contract also supports transactions that are managed inside an EIS resource manager without the necessity of involving an external transaction manager. Because of the transaction management contract, a call to the EIS may be enclosed in an XA transaction (a transaction type defined by the distributed transaction processing specification created by The Open Group). XA transactions are global: they can contain calls to multiple EISs, databases, and enterprise bean business methods. Although often appropriate, XA transactions are not mandatory. Instead, an application can use local transactions, which are managed by the individual EIS, or it can use no transactions at all.
The security management contract provides mechanisms for authentication, authorization, and secure communication between a J2EE server and an EIS to protect the information in the EIS.
The J2EE Connector architecture defines system contracts between a Java EE server and an EIS that enable inbound connectivity from the EIS: pluggability contracts for message providers and contracts for importing transactions.
To enable external systems to connect to a Java EE application server, the Connector architecture extends the capabilities of message-driven beans to handle messages from any message provider. That is, message-driven beans are no longer limited to handling JMS messages. Instead, EISs and message providers can plug any message provider, including their own custom or proprietary message providers, into a Java EE server.
To provide this feature, a message provider or an EIS resource adapter implements the messaging contract, which details APIs for message handling and message delivery. A conforming resource adapter is assured of the ability to send messages from any provider to a message-driven bean, and it also can be plugged into a Java EE server in a standard manner.
The Connector architecture supports importing transactions from an EIS to a Java EE server. The architecture specifies how to propagate the transaction context from the EIS. For example, a transaction can be started by the EIS, such as the Customer Information Control System (CICS). Within the same CICS transaction, a connection can be made through a resource adapter to an enterprise bean on the application server. The enterprise bean does its work under the CICS transaction context and commits within that transaction context.
The Connector architecture also specifies how the container participates in transaction completion and how it handles crash recovery to ensure that data integrity is not lost.
This section describes how components use the Connector architecture Common Client Interface (CCI) API and a resource adapter to access data from an EIS.
Defined by the J2EE Connector architecture specification, the CCI defines a set of interfaces and classes whose methods allow a client to perform typical data access operations. The CCI interfaces and classes are as follows:
ConnectionFactory: Provides an application component with a Connection instance to an EIS.
Connection: Represents the connection to the underlying EIS.
ConnectionSpec: Provides a means for an application component to pass connection-request-specific properties to the ConnectionFactory when making a connection request.
Interaction: Provides a means for an application component to execute EIS functions, such as database stored procedures.
InteractionSpec: Holds properties pertaining to an application component’s interaction with an EIS.
Record: The superclass for the various kinds of record instances. Record instances can be MappedRecord, IndexedRecord, or ResultSet instances, all of which inherit from the Record interface.
RecordFactory: Provides an application component with a Record instance.
IndexedRecord: Represents an ordered collection of Record instances based on the java.util.List interface.
A client or application component that uses the CCI to interact with an underlying EIS does so in a prescribed manner. The component must establish a connection to the EIS’s resource manager, and it does so using the ConnectionFactory. The Connection object represents the actual connection to the EIS and is used for subsequent interactions with the EIS.
The component performs its interactions with the EIS, such as accessing data from a specific table, using an Interaction object. The application component defines the Interaction object using an InteractionSpec object. When the application component reads data from the EIS (such as from database tables) or writes to those tables, it does so using a particular type of Record instance: either a MappedRecord, an IndexedRecord, or a ResultSet instance. Just as the ConnectionFactory creates Connection instances, a RecordFactory creates Record instances.
Note, too, that a client application that relies on a CCI resource adapter is very much like any other Java EE client that uses enterprise bean methods.
For more information on the Connector architecture, see:
Connector 1.5 specification:
The Connector web site: