Introduction to Cassandra

Leave a comment

Cassandra is a distributed noSQL data store that offers high availability, data durability and horizontal scalability. Cassandra is eventually consistent like Amazon Dynamo and provides a column family data model like Google’s BigTable. Cassandra is not a drop in replacement for a traditional RDBMS, however for some use cases (big data, logging, etc..), Cassandra can make perfect sense.

Configuration

Cassandra will need to be configured once it has been downloaded and installed. There are a few considerations that need to be made up front: which nodes will be seed nodes and how to partition data across the nodes.

Seed Nodes

Seed nodes are nodes in a cluster that should be contacted by newly created nodes when joining the cluster. Once the new node has contacted the seed node, it can begin listening to Gossip traffic.

Data Partitioning

Data partitioning consists of three elements: a partitioner, replica placement strategy and topology.

Partitioner

When a new row is inserted into the cluster, a determination must be made as to which node(s) it should be written, this is done by the partitioner. Cassandra allows pluggable partitioners such as the RandomPartitioner and the ByteOrderedPartitioner. The RandomPartitioner (default) uses consistent hashing to distribute data across the cluster. The ByteOrderedPartitioner stores rows in an ordered manner and allows range scans to occur over the row keys.

Topology

The cluster topology consists of number of nodes, data centers and distribution of nodes on racks.

Replica Placement Strategy

The replication strategy tells Cassandra how to replicate data across the cluster. Having data live on more than one node in a cluster keeps keeps it durable in case of node failure. This easiest strategy to use is the SimpleStrategy which treats all nodes as if they are in the same data center. The replication factor is then used to define how many nodes in the cluster will contain a copy of the data. A replication factor of 2 means that a single row of data will live on two nodes in the cluster. The other major strategy is the NetworkTopologyStrategy which allows the definition of multiple data centers and can be used to replicate data to nodes in those data centers as a way to keep data durable. An example would be if there are three data centers defined: A, B, C. Each data center has four nodes and data should live on at least 1 node in each data center, the replication factor would be A:1, B:1, C:1.

Snitch

The snitch is the component that defines how nodes are grouped together in a cluster. The snitch can be used to define data centers and which nodes belong to each data center.

SimpleSnitch

The default snitch is the SimpleSnitch which groups all nodes in a single data center. This snitch works best when using the SimpleStrategy.

RackInferringSnitch

The RackInferringSnitch is useful if the IP address of each node can be broken down by rack and data center. The names of the data centers will be numbers from the IP address.  Here is how the IP address is broken down: 10.D.R.N (D=data center, R=rack, N=node)

PropertyFileSnitch

The PropertyFileSnitch can be used to name data centers and assign nodes. This snitch requires and external properties file named cassandra-topology.properties. Each line in the property file will have an entry like this: IP=DataCenter:RACK (10.10.10.1=MYDATACENTER:MYRACK)

Data Structure

Cassandra is often referred to as “schema-less”, but a structure can still be defined using keyspaces and column families. A keyspace is much like a RDBMS schema and provides a container for column families which are like tables. Unlike tables, column families do not have a set number of columns, allowing each row to define which columns exist on the fly. Allowing the rows to contain different columns can lead to unpredictable disk usage.

Keyspace

As stated above, the keyspace is a container of column families and serves the additional purpose of defining the “replica placement strategy”.

Column Family

The column family holds rows of data that can have varying numbers of columns. Each row stores the row key (primary key), the column name, the column value and a timestamp.

Cache

Key Cache

Each column family has a key cache that keeps keys in memory and defaults to a size of 200,000.

Row Cache

The row cache is used to keep entire rows in memory. This feature is disabled by default since it can have negative performance impacts for column families with very large rows.

Indexing

The row key is the only primary index supported in Cassandra, however there is support for secondary indices. Secondary indices are best suited for low cardinality data like state abbreviation and not email addresses.

Table as Index Pattern

One way to compensate for the weak secondary index support is to use the Table as Index pattern. This pattern removes the need to use built-in secondary indices by using tables that provide an inverted index. A drawback is that application code must be used to maintain indices.

Here is how the pattern works. An index table is created for each column in the indexed table that should have an index. As column values are added, the index table is queried for the row key that matches the value. The row key from the indexed table is then injected into the matching row in the index table. A delete works the opposite of an add and an update performs a delete and add operation.

Row

A row is not like a standard table row, each row may have a differing number of columns. Below is a diagram of a set of rows and how they would be stored. The first row has three columns and the last three only have two columns. Each row has a different set of columns which is why the column name and column value must be stored together. An easy way to think of a row is like a map where there is a key and a value.

Data Modeling

Data modeling with Cassandra is nothing like traditional data modeling. Designing a relational structure involves structuring data logically and normalizing the resulting tables. Data retrieval then involves writing queries to extract that data in a meaningful way. Cassandra requires that this process be reversed and the queries be designed before the structure is created. Another difference is that Cassandra recommends de-normalizing data to make reads and writes faster.

Cassandra does not use SQL like a traditional database, but instead uses CQL.

Operation

Data is stored to disk in a structure known as an SSTable (Sorted String Table). SSTables are immutable and can generate several files between compactions.

Reads

A read request must go through several stages prior to being fulfilled. Cassandra uses a bloom filter to quickly determine if a row key exists on a node. The bloom filter is a space efficient structure that lives entirely in memory and makes false negatives impossible, but false positives are possible. Next, the row key index is checked to see which SSTable contains the data.

Writes

When a record is sent to the cluster to be written, the row key is hashed to identify the node which will be the primary node and then additional nodes are determined. Once a node receives the write request, the record is immediately written to the commit log. The commit log is read from upon node start to recover any writes that had yet to be written to SSTables prior to node shutdown. Once the record is written to the commit log, it is written to a memtable which lives in memory. The memtables are written to disk in the form of SSTables after a threshold has been reached.

Deletes

Records do not actually get deleted, but rather tombstoned. Records will get deleted during major compactions.

Compaction

Compaction is a way for Cassandra to merge multiple SSTables into a single SSTable. This process is very heavy on CPU and memory.

Hinted Handoff

Hinted Handoff is the process of passing write operations to a node that was down during normal operations.

Repair

A repair should be executed on nodes to fix any data discrepancies that may occur.

Eventual Consistency

As data gets written to the cluster, there may be a need to write the same row to multiple nodes based on the replication factor. There is no guarantee that all nodes get the data at the same time. A read request sent to one node may get an answer that is completely different than a read request sent to another node. Eventually all nodes will have the same data and become consistent. Cassandra does allow the application to define a consistency level when a query is issued. This allows for a read request to specify that all nodes must agree before the data is returned at the cost to performance.

Tools

There are several tools that come with Cassandra:

  • CQLSH – This tool is used to interact with the cluster using CQL.
  • cassandra-cli – This tool allows interaction with the cluster.
  • nodetool – This tool can provide statistics about the cluster.

Resources

Homepage – http://cassandra.apache.org/

Commercial Offering – http://www.datastax.com

JNA – https://github.com/twall/jna

NodeTool – http://wiki.apache.org/cassandra/NodeTool

Introduction to Java Persistence API

1 Comment

The Java Persistence API (JPA) was created to provide a standard Object Relational Mapping (ORM) solution for Java by combining the Java Data Objects API (JDO) and Entity beans from the Enterprise JavaBeans (EJB) specification. JPA technology allows data management by converting regular Java classes into entities and using an EntityManager. An entity may be defined by annotating the class or providing an XML mapping. Queries may be defined using an annotation, XML markup file or using the Criteria API. Although JPA is a part of the Java Enterprise Edition (JEE) specification, it does not require an application server or client container. Hibernate and EclipseLink (formerly TopLink) provide implementations of the JPA specification and provide stand alone implementations.

Entites
Any class may become an entity as long as it has a no argument constructor, is not declared final, has no persistent fields declared final and persistable fields should only be accessed using methods. Here is a simple example of a class that would be a good candidate for an entity:

import java.io.Serializable;

public class Customer implements Serializable
{
	private Integer customerId;
	private String firstName;
	private String lastName;
	private String emailAddress;
	private String accountNumber;

	public Integer getCustomerId()
	{
		return customerId;
	}

	public void setCustomerId(Integer customerId)
	{
		this.customerId = customerId;
	}

	public String getFirstName()
	{
		return firstName;
	}

	public void setFirstName(String firstName)
	{
		this.firstName = firstName;
	}

	public String getLastName()
	{
		return lastName;
	}

	public void setLastName(String lastName)
	{
		this.lastName = lastName;
	}

	public String getEmailAddress()
	{
		return emailAddress;
	}

	public void setEmailAddress(String emailAddress)
	{
		this.emailAddress = emailAddress;
	}

	public String getAccountNumber()
	{
		return accountNumber;
	}

	public void setAccountNumber(String accountNumber)
	{
		this.accountNumber = accountNumber;
	}
}

This is a simple class that follows the JavaBean convention. The class implements java.io.Serializable, but is only required to do so in the case where the entity is detached and given to a client over a network connection. There are two methods to declare a class an entity, the first method requires that the class be annotated as in this example:

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(schema="myschema", name="customer_table")
public class Customer implements Serializable
{
	@Id
	@Column(name="CUSTOMER_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ID_SEQ")
	@SequenceGenerator(name = "CUSTOMER_ID_SEQ", schema = "myschema", sequenceName = "customer_id_seq", allocationSize = 1)
	private Integer customerId;
	@Column(name="FIRST_NAME")
	private String firstName;
	@Column(name="LAST_NAME")
	private String lastName;
	@Column(name="EMAIL_ADDRESS")
	private String emailAddress;
	@Column(name="ACCOUNT_NUMBER")
	private String accountNumber;

	//Begin accessor and mutator methods here...
}

The @Entity annotation is required to mark this class as an entity and the @Table annotation defines the database table where the data will be stored/retrieved. Each field in the class that is to be persisted will also require an @Column annotation with the name of the column that holds the data. The column that represents the primary key is required to use the @Id annotation or in the case of multiple fields, use a composite key class.

There may be an instance where the domain class should not be annotated, in this case JPA provides an XML syntax. The XML must be referenced from the persistence.xml file used to configure JPA. Here is an example of the XML required to configure the Customer domain object:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 

http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"

				     version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<attributes>
			<id name="customerId" access="FIELD">
				<column name="CUST_ID"/>
				<generated-value strategy="SEQUENCE" generator="CUSTOMER_ID_SEQ"/>
				<sequence-generator name="CUSTOMER_ID_SEQ" schema="myschema" sequence-name="customer_id_seq" allocation-size="1"/>
			</id>
			<basic name="emailAddress" access="FIELD">
				<column name="EMAIL_ADDRESS"/>
			</basic>
			<basic name="firstName" access="FIELD">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName" access="FIELD">
				<column name="LAST_NAME"/>
			</basic>
			<basic name="accountNumber" access="FIELD">
				<column name="ACCOUNT_NUMBER"/>
			</basic>
		</attributes>
	</entity>
</entity-mappings>

Everything is defined the same as when annotations were used, but the definitions now reside in a separate XML file. The developer has been given control to decide which approach is best suited for the task at hand.

Queries
Once a class has been identified as an entity, queries will need to be defined to manage the data. There are three methods for defining queries: annotations, XML and the Criteria API. The Criteria API is beyond the scope of this article.

Here is an example of using annotations to define queries:

import java.io.Serializable;
import javax.persistence.*;

@Entity
@Table(schema="myschema", name="customer_table")
@NamedQueries(value = {
@NamedQuery(name = "customerQuery", query = "Select c from Customer c where c.emailAddress = :email")
})
public class Customer implements Serializable
{
	@Id
	@Column(name="CUSTOMER_ID")
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ID_SEQ")
	@SequenceGenerator(name = "CUSTOMER_ID_SEQ", schema = "myschema", sequenceName = "customer_id_seq", allocationSize = 1)
	private Integer customerId;
	@Column(name="FIRST_NAME")
	private String firstName;
	@Column(name="LAST_NAME")
	private String lastName;
	@Column(name="EMAIL_ADDRESS")
	private String emailAddress;
	@Column(name="ACCOUNT_NUMBER")
	private String accountNumber;

	//Begin accessor and mutator methods here...
}

The @NamedQuery annotation is used to define the query that may be used at a later time. The name attribute is used to help identify the query. The query attribute defines the JPQL statement that will be executed. Note that the @NamedQuery annotation is within the @NamedQueries annotation. This is so that multiple queries may be grouped and is not required. In addition to the @NamedQuery annotation there is also the @NamedNativeQuery annotation which allows for the definition of database specific queries.

When annotations are not being used, queries may be added to the entity mapping XML file. Here is the entity mapping file from an earlier example with the customerQuery:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 

http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"

version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<named-query name="customerQuery">
			<query>
			  Select c from Customer c where c.emailAddress = :email
			</query>
		</named-query>
		<attributes>
			<id name="customerId" access="FIELD">
				<column name="CUST_ID"/>
				<generated-value strategy="SEQUENCE" generator="CUSTOMER_ID_SEQ"/>
				<sequence-generator name="CUSTOMER_ID_SEQ" schema="myschema" sequence-name="customer_id_seq"
														allocation-size="1"/>
			</id>
			<basic name="emailAddress" access="FIELD">
				<column name="EMAIL_ADDRESS"/>
			</basic>
			<basic name="firstName" access="FIELD">
				<column name="FIRST_NAME"/>
			</basic>
			<basic name="lastName" access="FIELD">
				<column name="LAST_NAME"/>
			</basic>
			<basic name="accountNumber" access="FIELD">
				<column name="ACCOUNT_NUMBER"/>
			</basic>
		</attributes>
	</entity>
</entity-mappings>

A third solution exists where the class may be annotated, but the queries exist only in the XML as in this example:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm 

http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"

version="2.0">
	<package>com.jtv.domain.customer</package>
	<entity class="com.jtv.domain.customer.Customer" access="FIELD" name="Customer">
		<named-query name="customerQuery">
			<query>
			  Select c from Customer c where c.emailAddress = :email
			</query>
		</named-query>
	</entity>
</entity-mappings>

This last example shows the flexibility that JPA provides when configuring entities and queries.

Usage with EJB
This section will provide high level instructions for using JPA with an EJB application (tested using GlassFish application server 3.1.x). The first step is to tell the EJB application that JPA will be used by creating a file named persistence.xml in the META-INF directory. Here is an example of the file:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
	<persistence-unit name="customer-db">
		<jta-data-source>jdbc/CustomerDB</jta-data-source>
		<mapping-file>/com/example/customer/persistence/jpa/customer.xml</mapping-file>
		<!-- Use this to log SQL statements -->
		<properties>
			<property name="eclipselink.logging.level" value="FINE"/>
		</properties>
	</persistence-unit>
</persistence>

Line 3 defines a persistence unit named customer-db that will be used later in the EJB class. Line 4 references the JDBC datasource that will be used when executing data queries. The container should already have defined the JDBC datasource prior to application deployment. Line 5 pulls in the external mapping file (ensure that the file exists on the classpath) discussed in the previous sections. This line is not required if the annotation method is used exclusively. Lines 7, 8 and 9 are used to log the SQL statements being issued to the database if the EclipseLink ORM tool is being used.
The application is now aware JPA will be used. The javax.persistence.EntityManager will need to be injected into the EJB as in this example:

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

@Stateless
public class CustomerDAO
{
	@PersistenceContext(unitName = "customer-db")
	private EntityManager em;

	public Customer getCustomerById(Integer customerId)
	{
	  return em.find(Customer.class, custId);
	}

	public Customer findCustomerByEmailAddress(String emailAddress)
	{
	  Query q = em.createNamedQuery("customerQuery");
	  q.setParameter("email", emailAddress.trim());
	  
	  return (Customer) q.getSingleResult();
	}
}

The @PersistenceContext annotation on line 9 tells the container that this class wishes to use JPA and specifies the persistence unit named customer-db that was defined in the persistence.xml file. Line 10 is the variable where the javax.persistence.EntityManager class will be injected. The getCustomerById method shows an example of finding an entity without using a query by using the Id field (this field was identified when the entity was defined). The findCustomerByEmailAddress method shows an example of using the customerQuery query that was defined in the Queries section. The call on line 20 sets the parameter named email. Parameters are named instead of positional which improves code readability. These methods are very simple examples, but should an entity be passed to a client, the entity is required to implement java.io.Serializable and detached from the EntityManager using this call: em.detach(customer). Changes to a detached item can be saved using this call: em.merge(customer).

Resources
Introduction to Java Enterprise Edition
Introduction to Enterprise JavaBeans
Build Java Enterprise Artifacts with Maven

Introduction to Enterprise JavaBeans

1 Comment

Enterprise JavaBeans (EJB) technology is the server side component of the Java Enterprise Edition specification where the business logic of an application resides. Versions prior to 3.x were considered too cumbersome because they required developers to create multiple interfaces and XML files just to define a single bean. The 3.x version of the specification has improved the framework by using annotations and making XML configuration files optional in most cases. The introduction of the embedded enterprise bean container allows EJB components to be run outside of the application server without losing the benefits the application server provides. The EJB container provides services such as security, transactions, resource management, dependency injection, interceptors, concurrency and lookup so that the developer is free to focus on business logic.

There are four types of beans: Stateless session, Stateful session, Singleton and Message-Driven. The Entity bean is now a part of the Java Persistence API standard and will not be covered here. Below is a break down of each type of bean and an example.

Stateless session bean

The Stateless session bean (SLSB) is the most common bean used when programming EJBs. The bean holds state only during the invocation and is returned to the pool once the invocation is complete. A client may receive a different bean instance from the pool with each invocation as determined by the EJB container. The SLSB is ideal for light weight server calls.

import javax.ejb.Stateless;

@Stateless
public class MyStatelessBean
{
  public String echo(String name)
  {
    return "Hello " + name;
  }
}

The @Stateless annotation is all that is needed to mark this Java class as a SLSB. Additional annotations are provided to separate methods available to local (@Local) clients and remote (@Remote) clients. These annotations may be added at the class level specifying the interface class or at the interface level. Adding no additional annotations will allow the bean to be referenced locally, but the @Remote annotation is required if the bean is to be referenced remotely.

Stateful session bean

The Stateful session bean (SFSB) is different than the SLSB because it may hold state information while interacting with a client. The SFSB can have only one client and once the conversation ends or the container ends the session, the state is removed.

import javax.ejb.Stateful;

@Stateful
public class MyStatefulBean
{
  public String echo(String name)
  {
    return "Hello " + name;
  }
}

The @Stateful annotation is used to mark this Java class as a SFSB. As with the SLSB, additional annotations are provided to separate methods available to local (@Local) clients and remote (@Remote) clients.  These annotations may be added at the class level specifying the interface class or at the interface level. Adding no additional annotations will allow the bean to be referenced locally, but the @Remote annotation is required if the bean is to be referenced remotely.

Singleton session bean

The Singleton session bean (SSB) is different than the SLSB and SFSB in that there is only ever one instance at a time. This type of bean may be useful for fetching properties that can be used by other beans.

import javax.ejb.Singleton;

@Singleton
public class MySingletonBean
{
  public String echo(String name)
  {
    return "Hello " + name;
  }
}

The @Singleton annotation is used to mark this Java class as a SSB. The @Startup annotation may be used to create the instance during application startup.

Message-Driven bean

The Message-Driven bean (MDB) is different that the three types of session bean in that it is used for asynchronous communication. Once a client (usually Java Message Service) sends a request, it must check back for a response.

import javax.ejb.MessageDriven;

@MessageDriven
public class MyMessageDrivenBean
{
  public String echo(String name)
  {
    return "Hello " + name;
  }
}

The @MessageDriven annotation is used to mark a class as a MDB.

Dependency Injection
Dependency Injection has become a widely used pattern since the Spring framework burst onto the scene and the EJB specification has made it very easy to use. Adding a simple @EJB annotation tells the container to inject a bean into the requesting object. The example below shows how the Stateful bean example could be modified to use the Stateless bean to perform the simple echo method call.

import javax.ejb.Stateful;

@Stateful
public class MyStatefulBean
{
  @EJB
  private MyStatelessBean echoBean;

  public String echo(String name)
  {
    return echoBean.echo(name);
  }
}

Additional Resources

Building Java Enterprise Edition artifacts with Maven

Enterprise JavaBeans 3.1

Build Java Enterprise Edition artifacts with Maven

4 Comments

The older versions of the Java Enterprise Edition (JEE) specification made it difficult for developers to write, test and build applications. This problem has been resolved with the latest JEE specifications (tutorials version 5 and version 6) and has made life easier for developers. Applications are created using regular Java classes, annotations and the occasional XML file. Integrated Development Environments (IDE) make JEE applications quick to develop and test with push button deployments to local containers. Now that the development phase is easier, the focus needs to shift to continuous integration and producing artifacts for deployment to production and testing servers. Luckily there are plugins available for the Maven build tool that allow the creation of artifacts that can be deployed independent of the IDE.

The example project used will be a Maven multiple module project consisting of a parent project, an Enterprise Java Beans (EJB) project, a library project and an assembly project. Each project provides a basic example of what a typical JEE application will need.

Starting with the parent project, create a directory named jee-maven and create a file named pom.xml. Here are the contents of the pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>jee.example</groupId>
	<artifactId>jeemvn-parent</artifactId>
	<packaging>pom</packaging>
	<version>1.0</version>
	<name>JEE-Maven</name>
</project>

Test the build by running the command (assuming Maven has been installed properly): mvn clean install. The build should indicate success if everything is correct.

Building the parent project doesn’t do very much other than ensure that the project can be cleaned and installed. Some projects need to be created so that there is something to build. Create three directories, the first directory named assembly, the second directory named common and the last directory named echoservice. The assembly project will be used to create the deployment artifact, the common project will represent a library that can be shared across other projects and the echoservice project will be a simple EJB project.

In the common project, create the pom.xml file with these contents:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
				 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
				 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<parent>
		<groupId>jee.example</groupId>
		<artifactId>jeemvn-parent</artifactId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<name>JEE-Maven - Common</name>
	<artifactId>common</artifactId>
</project>

Create a directory named src/main/java/ to hold the example code that will be added a little later. Test the build by running the command: mvn clean install. During the build, Maven will create a new directory named target and in it a jar file named common-1.0.jar.

Create a pom.xml file in the echoservice directory with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
                                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
                <groupId>jee.example</groupId>
                <artifactId>jeemvn-parent</artifactId>
                <version>1.0</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
        <name>JEE-Maven - Echo Service</name>
        <artifactId>echoservice</artifactId>
	<dependencies>
		<dependency>
			<groupId>javax</groupId>
			<artifactId>javaee-api</artifactId>
			<version>6.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>jee.example</groupId>
			<artifactId>common</artifactId>
			<version>1.0</version>
		</dependency>
	</dependencies>
</project>

Test the build by running the command: mvn clean install. The build should succeed and a jar file named echoservice-1.0.jar will be created in the target directory. Notice the dependencies section. The first dependency makes JEE 6 available to the build and the second makes the newly created common project available. Now, create a directory named src/main/java/ to hold the source files.

The final step is to make the parent project aware of the children. This is done by simply adding the modules tag in the parent pom.xml file. This is how the file should look once the changes are made:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>jee.example</groupId>
	<artifactId>jeemvn-parent</artifactId>
	<packaging>pom</packaging>
	<version>1.0</version>
	<name>JEE-Maven</name>
        <modules>
                <module>common</module>
                <module>echoservice</module>
        </modules>
</project>

Now, when the command mvn clean install is executed, the output will indicate that all three projects have been built successfully. Here is a snippet of the output:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] JEE-Maven ......................................... SUCCESS [1.637s]
[INFO] JEE-Maven - Common ................................ SUCCESS [3.663s]
[INFO] JEE-Maven - Echo Service .......................... SUCCESS [0.614s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS

Now that the Maven configuration files are completed, the project can be imported into an IDE. Netbeans and Intellij can open the parent pom.xml file like a project file and allow coding immediately. Once the project has been imported into an IDE, create a new package in the common project named com.example.domain. Create a class named Echo that looks like this:

package com.example.domain;

import java.io.Serializable;

public class Echo implements Serializable
{
	private String value;

	public String getValue()
	{
		return value;
	}

	public void setValue(String value)
	{
		this.value = value;
	}
}

Execute the build from the jee-maven directory by executing the command mvn clean install and ensure that the build is still successful.

Next create an EJB in the echoservice project. Start by creating a new package named com.example.service. Then create a new java class named EchoServiceBean and add the required EJB annotation. The code for the bean should look like this:

package com.example.service;

import com.example.domain.Echo;

import javax.ejb.Stateless;

@Stateless
public class EchoServiceBean
{
	public String echo(Echo echo)
	{
		return "Hello " + echo.getValue();
	}
}

Execute the build from the jee-maven directory using mvn clean install and ensure that the build is successful.

That concludes the build phase of the project, but it is still not deployable. The assembly project will now need to be created to package the jar files into a deployable Enterprise Archive (ear). Create a new file named pom.xml under the assembly directory with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
				 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
				 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<parent>
		<groupId>jee.example</groupId>
		<artifactId>jeemvn-parent</artifactId>
		<version>1.0</version>
	</parent>

	<modelVersion>4.0.0</modelVersion>
	<artifactId>echo-service</artifactId>
	<packaging>ear</packaging>
	<name>Echo Service Assembly</name>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-ear-plugin</artifactId>
				<version>2.5</version>
				<!-- configuring the ear plugin -->
				<configuration>
					<version>5</version>
					<modules>
						<ejbModule>
							<groupId>jee.example</groupId>
							<artifactId>echoservice</artifactId>
						</ejbModule>
						<jarModule>
							<groupId>jee.example</groupId>
							<artifactId>common</artifactId>
							<bundleDir>lib</bundleDir>
						</jarModule>
					</modules>
				</configuration>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>jee.example</groupId>
			<artifactId>common</artifactId>
			<version>1.0</version>
			<type>jar</type>
		</dependency>
		<dependency>
			<groupId>jee.example</groupId>
			<artifactId>echoservice</artifactId>
			<version>1.0</version>
			<type>ejb</type>
		</dependency>
	</dependencies>
</project>

This file is slightly different from the other pom.xml files. The dependency tags now have a new type property that specifies whether this is a jar or ejb. Notice the use of the maven-ear-plugin. This plugin allows for the creation of the deployable artifact. Observe that the jarModule uses a bundleDir tag which puts the common library in a lib folder. More information about all the available options can be found here: maven-ear-plugin home.

Introduction to Java Enterprise Edition 6

2 Comments

Here is a post I contributed at work:

Introduction to JEE 6

This is the first in a series of articles that will provide a basic introduction to programming SOA using open source software.