4. Enterprise Java - J2EE

Abstract:

4.1. J2EE as application-container contract

The principal container responsibility is providing the glue that binds the client interfaces (Home and Remote) to the implementation provided in the Bean code. This relieves the server-side programmer from configuring the container adapters needed to activate the instance of the server object. The container is responsible for implementing the transmission of the client call to the appropriate instance of the implementation and for observing the transactional, synchronization, security, data integrity constraints implicitly required and explicitly configured.

  • mapping between Home and Remote interface methods visible to client and bean methods

    // remote interface - business methods
    public interface HistoryPersonRemote extends javax.ejb.EJBObject {
        public Integer getId() throws RemoteException;
        public String getName() throws RemoteException;
        public void setName(String str) throws RemoteException;
        public String getUniqueName() throws RemoteException;
        public void setUniqueName(String str) throws RemoteException;
    	public int getYOD() throws RemoteException;
    	public void setYOD(int yod) throws RemoteException;
    	public int getYOB() throws RemoteException;
    	public void setYOB(int yob) throws RemoteException;
        public String getDescription() throws RemoteException;
        public void setDescription(String str) throws RemoteException;
        public String getBio() throws RemoteException;
        public void setBio(String str) throws RemoteException;
    ...
    }
    
    // home interface creating, finding
    public interface HistoryPersonHomeRemote extends javax.ejb.EJBHome {
    
        public HistoryPersonRemote create(String uname)
            throws CreateException, RemoteException;
    
        public HistoryPersonRemote findByPrimaryKey(String pk)
            throws FinderException, RemoteException;
    
    	public Collection findByNamePart(String uname)
    		throws FinderException, RemoteException;
    
    	public HistoryPersonRemote findByName(String n)
            throws FinderException, RemoteException;
    
    	public Collection findByLifeSpan(int y1, int y2)
            throws FinderException, RemoteException;
    
    	public Collection findBornBefore(int y)
            throws FinderException, RemoteException;
    ...
    }
    
    // bean implementation
    public
    //abstract
    class HistoryPersonBean implements javax.ejb.EntityBean {
    
    private javax.sql.DataSource m_ds = null;
    private HistoryCategoriesRemote m_hcat = null;
    
    
    private EntityContext m_ctx = null;
    
    public String ejbCreate(String uname)
    	throws CreateException
    {}
    public void ejbPostCreate(String uname)
    {
    }
    
    public String ejbFindByPrimaryKey(String pk)
    	throws FinderException, RemoteException
    {
    	return ejbFindByName(pk);
    }
    
    public Collection ejbFindByNamePart(String uname)
    	throws FinderException, RemoteException
    {
    }
    public void ejbActivate()
    {
    	// Not implemented.
    }
    
    public void ejbPassivate()
    {
    	// Not implemented.
    }
    
    public void ejbLoad()
    {
    	String pk = (String)m_ctx.getPrimaryKey();
    	log("ejbLoad: " + pk);
    	java.sql.Connection c = Utils.getDBConnection(m_ds);
    	if ( c == null )
    		throw new EJBException("Cant connect to DB");
    ...
    }
    
    public void ejbStore()
    {
    	log("ejbStore: " + m_uname + " dirty? " + m_dirty);
    }
    
    public void ejbRemove()
    {
    	log("ejbRemove");
    }
    
    // business methods from Remote interface - would be abstract for CMP bean
    public void setYOD(int yod)
    {
    	m_yod = yod;
    	m_dirty = true;
    }
    public int getYOD( )
    {
    	return m_yod;
    }
    
    public void setYOB(int yob)
    {
    	m_yob = yob;
    	m_dirty = true;
    }
    public int getYOB( )
    {
    	return m_yob;
    }
    
    ...
    }
    
  • calling the ejbLoad and ejbStore methods on Entity beans

  • calling ejbPassivate and ejbActivate on Entity and Session beans to control memory usage

  • enforcing transactions. In the example below the session method is invoked by the container which will commit transaction only after the bean method returns without exception

    public class HistorySessionBean implements javax.ejb.SessionBean /*, javax.ejb.SessionSynchronization */{
    
    // example of transactional method in a session
    public void createPerson( HistoryPersonHomeRemote home_persons, String uname, String name, int y1, int y2)
    	throws RemoteException, CreateException
    {
    	log("createPerson -start");
    	HistoryPersonRemote hp;
    	hp = (HistoryPersonRemote)home_persons.create( uname );
    	hp.setName( name );
    	hp.setYOB( y1 );
    	hp.setYOD( y2 );
    	log("createPerson -end");
    }
    ...
    }
    
  • enforcing synchronization. The bean implementor is not supposed to used synchronization, create threads. Those are provided by the caller which always under the control of the container. Bean application is not aware of mechanisms security, transactions, thread synchronization, content synchronization. These are enforced by the container as declared in the configuration files.

Declarative programming through configuration files:

<?xml version="1.0"?>

<!DOCTYPE ejb-jar PUBLIC
'-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN'
'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
  <enterprise-beans>
    <entity>
      <display-name>HistoryPerson Bean</display-name>
      <ejb-name>HistoryPerson</ejb-name>
      <home>tjg.history.HistoryPersonHomeRemote</home>
      <remote>tjg.history.HistoryPersonRemote</remote>
      <ejb-class>tjg.history.HistoryPersonBean</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.String</prim-key-class>
      <reentrant>False</reentrant>
      <ejb-ref>
        <ejb-ref-name>ejb/HistoryCategories</ejb-ref-name>
        <ejb-ref-type>Entity</ejb-ref-type>
      	<home>tjg.history.HistoryCategoriesHomeRemote</home>
      	<remote>tjg.history.HistoryCategoriesRemote</remote>
        <ejb-link>HistoryCategories</ejb-link>
      </ejb-ref>
<!--
      <resource-ref>
		<description>History database PostgreSQL</description>
        <res-ref-name>jdbc/HistoryDataSource</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
      </resource-ref>
-->
	  <env-entry>
	  	<env-entry-name>HistoryDataSource</env-entry-name>
	  	<env-entry-type>java.lang.String</env-entry-type>
	  	<env-entry-value>PostgresHistoryDS</env-entry-value>
	  </env-entry>
    </entity>
    <entity>
      <display-name>HistoryEvent Bean</display-name>
      <ejb-name>HistoryEvent</ejb-name>
      <home>tjg.history.HistoryEventHomeRemote</home>
      <remote>tjg.history.HistoryEventRemote</remote>
      <ejb-class>tjg.history.HistoryEventBean</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.String</prim-key-class>
      <reentrant>False</reentrant>
      <ejb-ref>
        <ejb-ref-name>ejb/HistoryCategories</ejb-ref-name>
        <ejb-ref-type>Entity</ejb-ref-type>
      	<home>tjg.history.HistoryCategoriesHomeRemote</home>
      	<remote>tjg.history.HistoryCategoriesRemote</remote>
        <ejb-link>HistoryCategories</ejb-link>
      </ejb-ref>
<!--
      <resource-ref>
        <res-ref-name>jdbc/demoPool</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
      </resource-ref>
-->
	  <env-entry>
	  	<env-entry-name>HistoryDataSource</env-entry-name>
	  	<env-entry-type>java.lang.String</env-entry-type>
	  	<env-entry-value>PostgresHistoryDS</env-entry-value>
	  </env-entry>
    </entity>
    <entity>
      <ejb-name>HistoryCategories</ejb-name>
      <home>tjg.history.HistoryCategoriesHomeRemote</home>
      <remote>tjg.history.HistoryCategoriesRemote</remote>
      <ejb-class>tjg.history.HistoryCategoriesBean</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.String</prim-key-class>
      <reentrant>False</reentrant>
<!--
      <resource-ref>
        <res-ref-name>jdbc/demoPool</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
      </resource-ref>
-->
	  <env-entry>
	  	<env-entry-name>HistoryDataSource</env-entry-name>
	  	<env-entry-type>java.lang.String</env-entry-type>
	  	<env-entry-value>PostgresHistoryDS</env-entry-value>
	  </env-entry>
    </entity>
    <session>
      <ejb-name>HistorySession</ejb-name>
      <home>tjg.history.HistorySessionHomeRemote</home>
      <remote>tjg.history.HistorySessionRemote</remote>
      <ejb-class>tjg.history.HistorySessionBean</ejb-class>
	  <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
	</session>

	<message-driven>
	<ejb-name>HistoryActivity</ejb-name>
	<ejb-class>
		tjg.history.HistoryActivityMsg
	</ejb-class>
	<transaction-type>Container</transaction-type>
	<acknowledge-mode>Auto-acknowledge</acknowledge-mode>
	<message-driven-destination>
		<destination-type>javax.jms.Queue</destination-type>
	</message-driven-destination>
	</message-driven>

  </enterprise-beans>

  <assembly-descriptor>
    <container-transaction>
      <method>
        <ejb-name>HistoryPerson</ejb-name>
		<method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>

	<container-transaction>
      <method>
        <ejb-name>HistoryEvent</ejb-name>
		<method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>

	<container-transaction>
      <method>
        <ejb-name>HistoryCategories</ejb-name>
		<method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>

	<container-transaction>
      <method>
        <ejb-name>HistorySession</ejb-name>
		<method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>

    <container-transaction>
      <method>
        <ejb-name>HistoryActivity</ejb-name>
		<method-name>*</method-name>
      </method>
      <trans-attribute>Supports</trans-attribute>
    </container-transaction>

  </assembly-descriptor>

<!--  <ejb-client-jar>ejb20_basic_beanManaged_client.jar</ejb-client-jar> -->

</ejb-jar>

Not shown above are configuration items to CMP Entity beans like cmp-field, ejb-relation and security-role useful with Session beans.

4.2. Types of EJB's as different programming styles

EJB types diagram

Entity Beans are essentially in-memory objects that reflect the content of a database record. As such they are in fact caching the content of the database reducing server latency and load on the database. The older specification supports only BMP (Bean-Managed Persistence) Entity beans, which have to include code to perform ejbLoad/ejbStore operations as well as code for accessor/mutator (get/set) methods.

A newer approach to defining Entity beans is Container Managed Persistence - CMP. This is only becoming well-supported recently. The approach is declarative to a large degree. The programmer leaves the mutator/accessor methods abstract - they are declared to be related to certain declared fields of the EJB (and fields in a database table). Also declarations are used to establish relationships between Entity bean fields and database fields. Furthermore, other complex declarations establish the entity relationships describing the nature of the data in the database. These declarations will direct the actions of the container in optimizing the load and store operations that update and persist a bean's content.

Session Beans reflect procedures. They come in two flavors - stateful and stateless. The stateless variety is purely procedural - they dont retain caller-related state between the invocation and return. Their main usefulness is in imposing transactional integrity on a set of operations typically involving other beans and databases. The stateful bean reflects a compound procedure - a session. It captures the state of a client application in keeps it in the server. The state is of course shared between the methods of the bean unlike in the stateless session bean where it extends over one method invocation.

Message Driven Beans are asynchronous message processors. They register themselves as consumers on a message queue, receive messages and process them. There is no caller and no inherited security role and no inherited transaction. The client and server are really sender and receiver/replier and each of them can manage their behavior using apporpriate security and transaction infrastructure not to mention the standard programming techniques such as return values and exceptions.

4.3. Servlets - Web interface helpers

Servlets and JSP pages are not technically part of the J2EE platform but are fulfilling the function of interfacing to the HTTP protocol which is made so important by the prevalence of Web browsers. An HttpServlet implements methods that provide the Web servers behavior when it is contacted by HTTP clients with requests such GET, POST, etc. Servlets host the Web client for whom all the services of the container are available - JNDI, EJBs, JCA (eg database connectivity). A servlet is secured by declarations in it deployment descriptor or may be secured by its own measures using the teh java.security package interfacing to container-configured authentication services. A servlet has also methods making it easy to track HTTP sessions.

Example code snippets and pieces of configuration files:

/** HttpServlet method implemented
*/
public void doGet(HttpServletRequest req, HttpServletResponse res)
	throws IOException
{
	PrintWriter out;
	ByteArrayOutputStream bout;
	String title = "Files Servlet";
	String username = null;
	String username_html = null;
	int i;

	// authorization level
	int auth_level = 0;

	String what = req.getParameter("what");

	java.util.Date now = new java.util.Date();
	log("Request from [" + req.getRemoteAddr() + "][" + now.toString() + "]: "
		+ what + " " + req.getPathInfo());

	HttpSession session = req.getSession();
	String sessionId = session.getId();
	SessionClient sclient = null;

	Principal prince = req.getUserPrincipal();
	if ( prince != null )
	{
		username = prince.getName();
		log( "username from principal: " + username + " sessionId: " + sessionId);
		username_html = Utils.legalHTML( username );
		if ( m_sessionclient != null )
			sclient = m_sessionclient.makeNew( username );
	}
	else
	{
		log( "no principal");
	}

	boolean bCanDo1 = req.isUserInRole("poweruser");
	if ( bCanDo1 )
	{
		log( "user canDo1");
		auth_level = 1;
	}
	else
		log( "user NOT in canDo1");

	boolean bCanDo0 = true;

	if ( what == null && bCanDo0 )
	{
		processFileRequest( auth_level, req, res );
		return;
	}

.....


<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <display-name>Simple Web Applications</display-name>
    <context-param>
      <param-name>name</param-name>
      <param-value>my_files</param-value>
    </context-param>
  <servlet>
    <servlet-name>Research</servlet-name>
  	<display-name>Research Servlet</display-name>
    <jsp-file>/research.jsp</jsp-file>
    <init-param>
      <param-name>dbase_jndiname</param-name>
      <param-value>java:/PostgresHistoryDS</param-value>
    </init-param>
    <init-param>
      <param-name>logout</param-name>
      <param-value>/files/L?what=logout</param-value>
    </init-param>
  </servlet>
  <servlet>
    <servlet-name>Files</servlet-name>
  	<display-name>Files Servlet</display-name>
    <servlet-class>tjg.filesservlet.Files</servlet-class>
    <init-param>
      <param-name>logfile</param-name>
      <param-value>/home/jboss/files.log</param-value>
    </init-param>

.....

  <servlet-mapping>
    <servlet-name>Files</servlet-name>
    <url-pattern>/F/*</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>Research</servlet-name>
    <url-pattern>/R/research.jsp</url-pattern>
  </servlet-mapping>
<!--
  <servlet-mapping>
    <servlet-name>Login</servlet-name>
    <url-pattern>/L/*</url-pattern>
  </servlet-mapping>
-->

<session-config>
<session-timeout>10</session-timeout>
</session-config>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

<security-constraint>
<web-resource-collection>
<web-resource-name>Files</web-resource-name>
<description>Sensitive operations</description>
<url-pattern>/F/*</url-pattern>
<url-pattern>/R/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<http-method>PUT</http-method>
<http-method>DELETE</http-method>
</web-resource-collection>

<auth-constraint>
<description>Access to Files Servlet</description>
<role-name>poweruser</role-name>
</auth-constraint>

</security-constraint>

<login-config>
<auth-method>FORM</auth-method>
<realm-name>Files Login</realm-name>
<form-login-config>
<form-login-page>/LoginForm.html</form-login-page>
<form-error-page>/LoginError.html</form-error-page>
</form-login-config>
</login-config>

....

4.4. J2EE server structure and essential container services

Application Server diagram

The important aspect of container-provided services is that they can be implemented by various vendors but only in a way conforming to interface specifications in the Java and J2EE. Those specifications are a model of good software design.

JNDI services are crucial for clients who need to find objects that a server binds to published names. For example a servlet would need to find the "home" interface of an EJB by picking up it is name in its own configuration - for example a name of a database connection source.

JAAS services provide authentication and authorization that has an interface independent of the underlying technology. The authentication is a process of attaching Principals and Credentials to a Subject. A server application such as a servlet can implement its own authentication scheme in a manner similar to PAM. Once the Subject is authenticated the methods on secured servlets or EJBs are invoked by the container that provides the authentication information in the call context. From there the code in an EJB can still extract information about the invoking Principal and check if it is in an authorized role. The role-based authorization is very well supported by the API.

JTA is the code word for the transaction manager. This is the piece of J2EE that propagates transactional call properties (ACID) observing transactional attributes declared by individual EJBs and their methods. Those transactional attributes are: Required, RequiredNew, Supports, NotSupported, Mandatory, Never.

JCA - implementation of the abstract javax.sql.DataSource which manages a pool of database connections.

JMS - message queue implementation accords to some javax.... specification.

Scheduler - is not an official part of J2EE (not specified by java../J2EE interface) but is provided by JBoss and similar to cron. I can invoke perl scripts from it.

Implementations are many. Free implementations - Suns reference implementation, JBoss free and open-source. Others are from BEA Weblogic and IBM Websphere. Tools are bundled with many of them except JBoss for which you have to buy books with documentation - that's their business model. Some come with databases (also pure Java databases) - I am using JBoss with Jetty web server and PostgreSQL database.

Look at my History application as example.