7/29/2011

Example of JPA and Hibernate with JBoss and MySql

This post demonstrates how to write a simple web app using Java Persistence API (JPA), EJB 3 session bean and servlet, connecting to MySql database, and deployed to JBoss appserver 6.

1, JPA entity class, Greeting, is the same as in my previous example, also included here in its entirety:

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

@Entity
public class Greeting implements Serializable {
@Id @GeneratedValue private int id;
@Basic private String message;
@Basic private String language;

public Greeting() {}

public Greeting(String message, String language) {
this.message = message;
this.language = language;
}

public String toString() {
return "Greeting id=" + id + ", message=" + message +
", language=" + language;
}
}
2, a stateless session bean facade for CRUD operations with container-managed transaction and container-managed persistence context:
package test;
import javax.ejb.*;
import javax.persistence.*;

@Stateless
public class GreetingBean {
@PersistenceContext
private EntityManager em;

public void create(Greeting... gs) {
for(Greeting g : gs) {
em.persist(g);
}
}

public Greeting findByLang(String lang) {
return (Greeting) em.createQuery(
"select g from Greeting g where g.language = :language")
.setParameter("language", lang).getSingleResult();
}
}
3, TestServlet, which calls the stateless session bean:
package test;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.ejb.EJB;

@javax.servlet.annotation.WebServlet(urlPatterns = "/*")
public class TestServlet extends HttpServlet {
@EJB
private GreetingBean bean;

protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
Greeting g_en = new Greeting("hello world", "en");
Greeting g_es = new Greeting("hola, mundo", "es");
bean.create(g_en, g_es);
out.println("Created and persisted " + g_en + ", and " + g_es);

Greeting g = bean.findByLang("en");
out.println("Query returned: " + g);
}

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
}
4, persistence.xml with JTA transaction using a MySQL datasource:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="greeting-pu" transaction-type="JTA">
<!--
<jta-data-source>java:/DefaultDS</jta-data-source>
-->
<jta-data-source>java:/MySqlDS</jta-data-source>
<properties>
<!--
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
-->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
</properties>
</persistence-unit>
</persistence>
5, create the MySql datasource in JBoss appserver:
cp $JBOSS_HOME/docs/examples/jca/mysql-ds.xml $JBOSS_HOME/server/default/deploy/
# change user-name and password elements in mysql-ds.xml
cp $HOME/downloads/mysql-connector-java-5.1.15-bin.jar $JBOSS_HOME/server/default/lib/
6, compile and package class files and persistence.xml into test.war:
javac -d ../ -cp "$JBOSS_HOME/client/*" *.java

content of test.war:
====================
WEB-INF/classes/META-INF/persistence.xml
WEB-INF/classes/test/Greeting.class
WEB-INF/classes/test/GreetingBean.class
WEB-INF/classes/test/TestServlet.class
7, start MySql database server, JBoss default server and deploy test.war:
cd /usr/local/mysql-5.1.32
sudo bin/mysqld_safe --user root

$JBOSS_HOME/bin/run.sh
8, deploy and run the test:
cp test.war $JBOSS_HOME/server/default/deploy
curl http://localhost:8080/test/
If you use the default HyperSonic embedded database in JBoss AS, instead of MySql database server, make the following adjustments:

(a), toggle the comments in persistence.xml;
(b), no need to copy mysql-ds.xml, or MySql driver jar;
(c), no need to start MySql database server.

7/05/2011

How to programmatically copy jar files

To copy a jar file programmatically in java involves the following steps:

1, create a JarOutputStream based on the destination jar file;

2, loop through all entries in the source jar file, and get the InputStream from each entry;

3, create a new jar entry with the same name as the source jar entry, and put the new entry to the JarOutputStream;

4, now we have InputStream, OutputStream, and the new jar entry in place, we are ready to transfer the bits, by reading bytes from InputStream into a buffer and writing buffer content to OutputStream.

5, close the InputStream, flush the JarOutputStream, and close the jar entry. This completes the copying of one jar entry.

6, after the loop ends, all entries are copied from source jar file to destination jar file. Finally, close the JarOutputStream.

package test;
import java.io.*;
import java.util.Enumeration;
import java.util.jar.*;

//java -cp . test.CopyZip xxx/lib/sac-1.3.jar /tmp
//
public class CopyZip {
public static void main(String[] args) throws Exception {
File sourceFileOrDir = new File(args[0]);
File destDir = new File(args[1]);
if (sourceFileOrDir.isFile()) {
copyJarFile(new JarFile(sourceFileOrDir), destDir);
} else if (sourceFileOrDir.isDirectory()) {
File[] files = sourceFileOrDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".jar");
}
});
for (File f : files) {
copyJarFile(new JarFile(f), destDir);
}
}
}

public static void copyJarFile(JarFile jarFile, File destDir) throws IOException {
String fileName = jarFile.getName();
String fileNameLastPart = fileName.substring(fileName.lastIndexOf(File.separator));
File destFile = new File(destDir, fileNameLastPart);

JarOutputStream jos = new JarOutputStream(new FileOutputStream(destFile));
Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
InputStream is = jarFile.getInputStream(entry);

//jos.putNextEntry(entry);
//create a new entry to avoid ZipException: invalid entry compressed size
jos.putNextEntry(new JarEntry(entry.getName()));
byte[] buffer = new byte[4096];
int bytesRead = 0;
while ((bytesRead = is.read(buffer)) != -1) {
jos.write(buffer, 0, bytesRead);
}
is.close();
jos.flush();
jos.closeEntry();
}
jos.close();
}
}
A common error is:
Exception in thread "main" java.util.zip.ZipException: invalid entry compressed size (expected 1665 but got 1680 bytes)
at java.util.zip.ZipOutputStream.closeEntry(ZipOutputStream.java:206)
at test.CopyZip.copyJarFile(CopyZip.java:63)
at test.CopyZip.main(CopyZip.java:37)
It usually occurs when text file entries in the source jar file contain some non-ASCII characters such as ^I ^Z ^D ^C. Some common files are META-INF/COPYRIGHT.html, META-INF/LICENSE.txt, etc, probably because these files were created in a non-ASCII editor but saved as text files. Open them in vi or vim to see these offending characters.

To avoid this type of ZipException, always create a new JarEntry with the same name, and pass it to putNextEntry() method. Do not pass the existing jar entry from the source jar file to putNextEntry() method.

7/03/2011

Servlet init method vs PostConstruct method

There are 2 initialization methods in servlet:

(1), init(ServletConfig config) methods defined in javax.servlet.Servlet interface. Servlet init method has been there since the first version of Servlet. This method was later (around Servlet 2.3) overloaded in javax.servlet.GeneircServlet for convenience. According to the above javadoc, the servlet container calls the init method exactly once after instantiating the servlet.

(2), PostConstruct method, as defined in javax.annotation.PostConstruct, was introduced in Java EE 5 to all component types, including all web components (Servlet 2.5). Similarly, @PostConstruct (or post-construct, if declared in deployment descriptors) methods are to be invoked only once after the component class is instantiated and before it is put into service.

But these two servlet methods are not exactly the same.

The name of Servlet init method is set in the interface, and it must be public. PostConstruct method is more flexible: it can be private, package default, protected, or public, and can be named anything.

I could not find docs specifying the invocation order and relationship of the two methods. But it turns out the order is:

servlet class constructor --> PostConstruct --> init and init(ServletConfig)
So inside PostConstruct method at the second step, ServletConfig has not been initialized, and neither is ServletContext. Calling getServletConfig() in PostConstruct returns null. Calling getServletContext() results in IllegalStateException in most servers (GlassFish 3, Tomcat 7 and JBoss 6), but returns null in Resin 4.

Apparently there are overlappings and subtle differences between PostConstruct and servlet init methods. I guess ideally we could merge them into one. If there is no PostConstruct method in servlet class, init() will be considered as its PostConstruct method; if developers want to specify a PostConstruct method, it has to be the same as init() method, basically adding the redundant @PostConstruct to init(). This is roughly how ejbCreate and PostConstruct is handled in EJB 3.

What about web apps that already have both PostConstruct and init methods? In order to accommodate them, the next desirable approach is to allow both, but clearly specify their relationship, making clear getServletConfig() and getServletContext() both return valid values inside the both methods.

The same applies to PreDestroy and servlet destroy method, to a lesser extent.