Monday, September 27, 2010

How to Create an Executable WAR

Have you ever wondered how Hudson let you run a WAR file as if you run an executable JAR file? The answer is simple, i.e. embed a web container inside a WAR file and create a META-INF/MANIFEST.MF that has Main-Class attribute. For Hudson, it embeds Winstone. In this example, I'm gonna use Jetty instead.

pom.xml
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.fredy</groupId>
  <artifactId>exec-war</artifactId>
  <version>0.1</version>
  <packaging>war</packaging>
  <name>exec-war</name>
  <dependencies>
    <dependency>
      <groupId>org.mortbay.jetty</groupId>
      <artifactId>jetty</artifactId>
      <version>6.1.25</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>myproject.Main</mainClass>
            </manifest>
          </archive>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
        <executions>
          <execution>
            <id>jar-with-dependencies</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

web.xml
<?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/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>exec-war</display-name>
  <session-config>
    <session-timeout>30</session-timeout>
  </session-config>
  <servlet>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>myproject.servlet.MyServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>
</web-app>

MyServlet.java
package myproject.servlet;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet {

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        process(req, resp);
    }

    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        process(req, resp);
    }

    private void process(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        resp.setContentType("text/html");
        PrintWriter pw = resp.getWriter();
        try {
            pw.print("<html><body><h1>Hello World</h1></body></html>");
        } finally {
            pw.close();
        }
    }
}

Main.class
package myproject;

import java.net.URL;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppContext;

public class Main {
    
    public static void main(String[] args) throws Exception {
        Server server = new Server();
        
        Connector connector = new SelectChannelConnector();
        connector.setPort(8888);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);
        
        URL warUrl = Main.class.getClassLoader().getResource("WEB-INF");
        System.out.println(warUrl);
        String warUrlString = warUrl.toExternalForm();
        server.setHandler(new WebAppContext(warUrlString, "/exec-war"));
        server.setStopAtShutdown(true);
        
        server.start();
    }
}

1. Package a WAR file
mvn package
2. Rename the package name from JAR to WAR. The JAR still has WAR structure. The file has a JAR extension because of the maven-assembly-plugin. So, it's basically a JAR file with a WAR structure inside.
3. Run it.
java -jar exec-war.war
4. Or deploy it in a web container, such as Tomcat.

My Open Source Projects

I've created two new open source projects.

FileRenamer - A small tool to do bulk renaming.
JUGen - A small tool to generate JUnit.

Do give a feedback! :)

Wednesday, September 22, 2010

Applying a License into Source Code

Below is a script to apply a license into source code.

ApplyLicense.groovy
validateArgs(this.args)

def projectDir = new File(this.args[0])
def licenseText = new File(this.args[1]).text
def fileExtension = this.args[2]

projectDir.eachFileRecurse { file ->
    if (file.name.endsWith(fileExtension)) {
        def tmpFile = new File(file.path + ".tmp")
        tmpFile.text = licenseText + file.text
        tmpFile.renameTo(file) 
    }
}

def validateArgs(def args) {
    if (args.size() < 3) {
        printUsage()
        System.exit(1)
    }
    if (!new File(args[0]).isDirectory()) {
        printUsage()
        System.exit(1)
    }
    if (!new File(args[1]).isFile()) {
        printUsage()
        System.exit(1)
    }
}

def printUsage() {
    println "Usage: groovy ApplyLicense.groovy <project_dir> <license_file> <file_extension>"
}

Getting Started with JAX-RS

The example below shows how to get started with JAX-RS with Jersey (JAX-RS RI). The client side uses jQuery 1.4.2 and jQuery UI 1.8.4. The nice thing about jQuery UI 1.8.4 is that it can style the buttons according to theme specified.

pom.xml
<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/maven-v4_0_0.xsd"> 
  <modelVersion>4.0.0</modelVersion> 
  <groupId>myproject</groupId> 
  <artifactId>jersey-app</artifactId> 
  <version>0.1</version> 
  <name>jersey-app</name> 
  <packaging>war</packaging> 
  <repositories> 
    <repository> 
      <id>maven2-repository.dev.java.net</id> 
      <name>Java.net Repository for Maven</name> 
      <url>http://download.java.net/maven/2/</url> 
      <layout>default</layout> 
    </repository> 
  </repositories> 
  <dependencies> 
    <dependency> 
      <groupId>com.sun.jersey</groupId> 
      <artifactId>jersey-server</artifactId> 
      <version>1.4</version> 
    </dependency> 
  </dependencies> 
  <build> 
    <plugins> 
      <plugin> 
        <groupId>org.apache.maven.plugins</groupId> 
        <artifactId>maven-compiler-plugin</artifactId> 
        <configuration> 
          <source>1.6</source> 
          <target>1.6</target> 
        </configuration> 
      </plugin> 
      <plugin> 
        <groupId>org.mortbay.jetty</groupId> 
        <artifactId>maven-jetty-plugin</artifactId> 
        <version>6.1.25</version> 
      </plugin> 
    </plugins> 
  </build> 
</project>

web.xml
<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" 
  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  id="WebApp_ID" version="2.5"> 
  <display-name>jersey-app</display-name> 
  <servlet> 
    <servlet-name>Jersey Web Application</servlet-name> 
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> 
    <init-param> 
      <param-name>com.sun.jersey.config.property.packages</param-name> 
      <param-value>myproject</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
  </servlet> 
  <servlet-mapping> 
    <servlet-name>Jersey Web Application</servlet-name> 
    <url-pattern>/ws/*</url-pattern> 
  </servlet-mapping> 
  <welcome-file-list> 
    <welcome-file>index.jsp</welcome-file> 
  </welcome-file-list> 
</web-app> 
The com.sun.jersey.config.property.packages tells the ServletContainer to scan which package that contains the JAX-RS classes.

HelloResource.java
package myproject;

import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.PathParam; 
import javax.ws.rs.Produces;

@Path("/hello/{user}") 
public class HelloResource { 
    
    @Produces("text/plain") 
    @GET 
    public String getMessage(@PathParam("user") String user) { 
        return "Hello World, " + user; 
    } 
}

DefaultHelloResource.java
package myproject;

import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.Produces;

@Path("/hello") 
public class DefaultHelloResource {

    @GET 
    @Produces("text/plain") 
    public String getMessage() { 
        return "Hello World"; 
    } 
}

index.jsp
<?xml version="1.0" encoding="ISO-8859-1" ?> 
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" 
    pageEncoding="ISO-8859-1"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> 
    <title>JAX-RS with Jersey</title> 
    <link type="text/css" href="css/ui-lightness/jquery-ui-1.8.4.custom.css" rel="stylesheet" /> 
    <link type="text/css" href="css/ui.jqgrid.css" rel="stylesheet" /> 
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> 
    <script type="text/javascript" src="js/jquery-ui-1.8.4.custom.min.js"></script> 
    <script type="text/javascript"> 
      $(function() { 
          $(".buttonStyle").button(); 
      });

      $(function() { 
          $("#button").click(function() { 
              $("#message").load("/jersey-app/ws/hello/" + $("#name").val()); 
          }); 
      }); 
    </script> 
    <style type="text/css"> 
        body { 
          font: 62.5% "Trebuchet MS", sans-serif; 
          margin: 50px; 
        } 
    </style> 
  </head> 
  <body> 
    <h1>Welcome to JAX-RS with Jersey Demo!</h1> 
    Name: <input id="name" type="text"/> 
    <button id="button" class="buttonStyle">Get Message</button> 
    <p></p> 
    <div id="message"/> 
  </body> 
</html>

Tuesday, September 21, 2010

How to Dynamically Load JAR Files

Suppose we have a Java application that need to load all the JAR files in a particular directory, e.g. plugins directory, here is how to do it.

plugin-api project
Hello.java
package myproject.api;

public interface Hello { 
    public String getMessage(); 
} 
Export this class as a JAR, e.g. plugin-api.jar


hello-impl project
HelloImpl.java
package myproject.api.impl;

import myproject.api.Hello;

public class HelloImpl implements Hello {

    @Override 
    public String getMessage() { 
        return "Hello World"; 
    } 
} 
Make sure the plugin-api.jar is in the hello-impl project classpath. Export the JAR as hello-impl.jar to main-project/plugins (see below).


bye-impl project
ByeImpl.java
package myproject.api.impl;

import myproject.api.Hello;

public class ByeImpl implements Hello {

    @Override 
    public String getMessage() { 
        return "Bye World"; 
    } 
} 
Make sure the plugin-api.jar is in the bye-impl project classpath. Export the JAR as bye-impl.jar to main-project/plugins (see below).


main project
Main.java
import java.io.File; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.Enumeration; 
import java.util.jar.JarEntry; 
import java.util.jar.JarFile;

import myproject.api.Hello;

public class Main {

    public static void main(String[] args) throws Exception { 
        for (File file : new File("plugins").listFiles()) { 
            URLClassLoader ucl = new URLClassLoader(new URL[] {file.toURI().toURL()}, 
                    Main.class.getClassLoader()); 
            Enumeration<JarEntry> jarEntries = new JarFile(file).entries(); 
            while (jarEntries.hasMoreElements()) { 
                JarEntry jarEntry = jarEntries.nextElement(); 
                if (jarEntry.getName().endsWith(".class")) { 
                    String className = jarEntry.getName().replace("/", 
                            ".").substring(0, 
                            jarEntry.getName().indexOf(".class")); 
                    Class c = Class.forName(className, true, ucl); 
                    for (Class i : c.getInterfaces()) { 
                        if ("myproject.api.Hello".equals(i.getName())) { 
                            Hello hello = (Hello) c.newInstance(); 
                            System.out.println(hello.getMessage()); 
                        } 
                    } 
                } 
            } 
        } 
    } 
} 
Make sure the plugin-api.jar is in the main project classpath. And create plugins directory and drop all the two implementation JARs into the plugins directory, such as below
main/plugins/hello-impl.jar 
             bye-impl.jar

Monday, September 20, 2010

SSH Login without Password

To login in SSH without password, we need to do the following:
1. Generate a key pair (private key and public key) in Server A
ssh-keygen -t rsa
2. Copy the public key from Server A to Server B.
scp id_rsa.pub testuser:[server_B_ip_address]:/home/testuser/.ssh
3. In Server B, add that public key into authorized_keys file.
cat id_rsa.pub >> authorized_keys
4. Verify it from Server A.
ssh testuser@[server_B_ip_address]

How to Setup NFS in Linux

Make sure we have the NFS (both client and server) package installed.
1. Edit /etc/exports
/home/myuser/share *rw(root_squash)
rw --> read write
ro --> read only
root_squash --> the remote root user is not allowed to have a root level access
no_root_squash --> the remote root user is allowed to have a root-level access
2. Verify it
showmount -e [nfs_server_address]
3. Open firewall port 2049
4. Restart the nfsserver deamon
/etc/init.d/nfsserver restart
5. Mount the remote export.
mount -t nfs [nfs_server_address]:/home/myuser/share nfs/

Friday, September 17, 2010

JVM Shutdown Hook

Suppose our Java application receives a SIGINT or SIGKILL signal and we need to do something before the JVM terminates. Java provides a way to add a shutdown hook into the JVM.
public class Main {

    static {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("Bye, bye JVM :(");
            }
        });
    }
    
    public static void main(String[] args) {
        System.out.println("Doing something....");
        try {
            Thread.sleep(100000);
        } catch (Exception e) {
        } 
    }
}
Make sure to add the shutdown hook in the static block.

How to Setup Samba in Linux

Make sure we have the Samba package installed. For example, we want to share the /tmp directory in Linux so that we can access it from our Windows or from another Linux client (NFS is better for Linux to Linux file sharing), we need to do the following:
1. Edit /etc/samba.conf add the following lines
[tmp]
    comment = Test
    path = /tmp
    writable = Yes
    public = Yes
2. Add a Samba user.
smbpasswd -a sambauser
Enter the password.
3. We need to open firewall for the following ports:
- 137 (netbios-ns)
- 138 (netbios-dgm)
- 139 (netbios-ssn)
- 445 (microsoft-ds)
For more info, see /etc/services
3. Restart smb daemon.
/etc/init.d/smb restart

For Windows client to access the shared directory:
1. Go to Run and enter \\[ip_address]\tmp
2. Enter the Samba username and password.

For Linux client to access the shared directory:
mount -t cifs //172.16.242.129/tmp -o username=sambauser,password=password samba/

How to Setup DNS in Linux

To setup DNS in Linux, make sure we have BIND package installed.
1. Add these lines in /etc/resolv.conf
domain fw-geekycoder.com
nameserver 172.16.242.129
2. Add forward and reverse zones in /etc/named.conf
zone "fw-geekycoder" in {
    type master;
    file "master/fw-geekycoder.zone";
};

zone "242.16.172.in-addr.arpa" in {
    type master;
    file "master/242.16.172.zone";
};
3. As you we can see in step two, there are two zone files that need to be created.
In /var/lib/named/master/fw-geekycoder.zone file
$TTL 1W
@       IN SOA  ns1.fw-geekycoder.com. root.fw-geekycoder.com. (
                                2010191505      ; serial (d. adams)
                                2D              ; refresh
                                4H              ; retry
                                6W              ; expiry
                                1W )            ; minimum

fw-geekycoder.com.      IN NS           ns1.fw-geekycoder.com.
ns1                     IN A            172.16.242.129
www                     IN A            172.16.242.129
gateway                 IN A            172.16.242.2
In 242.16.172.zone file
$TTL 1W
@       IN SOA  ns1.fw-geekycoder.com. root.fw-geekycoder.com. (
                                2010191605      ; serial (d. adams)
                                2D              ; refresh
                                4H              ; retry
                                6W              ; expiry
                                1W )            ; minimum

129.242.16.172.in-addr.arpa.    IN NS           ns1.fw-geekycoder.com.
129                             IN PTR          ns1.fw-geekycoder.com.
129                             IN PTR          www.fw-geekycoder.com.
2                               IN PTR          gateway.fw-geekycoder.com.
4. Restart the named daemon.
/etc/init.d/named restart

Wednesday, September 15, 2010

jqGrid TreeGrid with JSON

The example below shows how to use TreeGrid using jqGrid with JSON.

index.html
<!DOCTYPE html> 
<html> 
  <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> 
    <title>jqGrid Example Page</title> 
    <link type="text/css" href="css/smoothness/jquery-ui-1.8.4.custom.css" rel="stylesheet" /> 
    <link type="text/css" href="css/ui.jqgrid.css" rel="stylesheet" /> 
    <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> 
    <script type="text/javascript" src="js/jquery-ui-1.8.4.custom.min.js"></script> 
    <script type="text/javascript" src="js/i18n/grid.locale-en.js"></script> 
    <script type="text/javascript" src="js/jquery.jqGrid.min.js"></script> 
    <script type="text/javascript">     
      $(function() { 
          $("#treegrid").jqGrid({ 
              url: 'tree.json', 
              datatype: 'json', 
              mtype: 'GET', 
              colNames: ["ID", "Description", "Total"], 
              colModel: [{ 
                  name: 'id', 
                  index: 'id', 
                  width: 1, 
                  hidden: true, 
                  key: true 
              }, { 
                  name: 'desc', 
                  index: 'desc', 
                  hidden: false, 
                  sortable: true 
              }, { 
                  name: 'num', 
                  index: 'num', 
                  hidden: false, 
                  sortable: true 
              }], 
              treeGridModel: 'adjacency', 
              height: 'auto', 
              width: '500', 
              pager: "#ptreegrid", 
              treeGrid: true, 
              ExpandColumn: 'desc', 
              ExpandColClick: true, 
              caption: "Tree Grid Example" 
          }) 
      }); 
    </script> 
    <style type="text/css"> 
        body { 
          font: 62.5% "Trebuchet MS", sans-serif; 
          margin: 50px; 
        } 
    </style> 
  </head> 
  <body> 
    <h1>jqGrid Example</h1> 
    <h2>Tree Grid</h2> 
    <table id="treegrid"></table> 
    <div id="ptreegrid"></div> 
  </body> 
</html>

tree.json
{ 
    "page": 1, 
    "total": 1, 
    "records": 2, 
    "rows": [ 
       {"id": 1, "cell": ["1", "Super Item", "300", 0, null, false, false]}, 
           {"id": 2, "cell": ["2", "Item 1", "100", 1, 1, false, false]}, 
           {"id": 3, "cell": ["3", "Sub Item 1", "50", 2, 2, true, true]}, 
           {"id": 4, "cell": ["4", "Sub Item 2", "25", 2, 2, false, false]}, 
           {"id": 5, "cell": ["5", "Sub-sub Item 1", "25", 3, 4, true, true]}, 
           {"id": 6, "cell": ["6", "Sub Item 3", "25", 2, 2, true, true]}, 
           {"id": 7, "cell": ["7", "Item 2", "200", 1, 1, false, false]}, 
           {"id": 8, "cell": ["8", "Sub Item 1", "100", 2, 7, false, false]}, 
           {"id": 9, "cell": ["9", "Sub-sub Item 1", "50", 3, 8, true, true]}, 
           {"id": 10, "cell": ["10", "Sub-sub Item 2", "50", 3, 8, true, true]}, 
       {"id": 11, "cell": ["11", "Sub Item 2", "100", 2, 7, true, true]} 
    ] 
} 

Tuesday, September 7, 2010

Creating ISO Images

Creating ISO images in Linux is very straightforward.
If for example, we need to create an ISO image for a CD/DVD, we can use dd command.
dd if=/dev/cdrom of=cd.iso

If we need to create an ISO image out for a directory/file, we can use mkisofs command.
mkisofs -o ~/docs.iso -J -r -V MY_LABEL ~/Documents

To verify if the ISO image is correct, we need to mount that ISO image.
mount -t iso9660 -o loop /media

To umount:
umount /media
See man dd and mkisofs for more information on the above commands.