Standalone Java REST client

We write a standalone Java REST client for the RESTful service described in this post – http://cuppajavamattiz.com/2012/11/09/a-simple-restful-service/. The code can be integrated into a web application as well when the need to consume a RESTful service is required.


package com.mattiz.json.rest.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

public class JsonRestful {

	StatusLine statusLine = null;

	public InputStream processRequest(String baseUrl, String contextPath,
			String requestBody, String contextParam, String requestType)
			throws Exception {
		HttpResponse response = null;
		InputStream responseStream = null;
		try {
			HttpParams httpParameters = new BasicHttpParams();
			HttpConnectionParams.setConnectionTimeout(httpParameters, 100000);
			HttpConnectionParams.setSoTimeout(httpParameters, 100000);
			HttpClient httpclient = new DefaultHttpClient(httpParameters);
			String url = makeUrl(baseUrl, contextPath, requestBody,
					contextParam);
			if (requestType.equals("GET")) {
				HttpGet httpGet = new HttpGet(url);
				httpGet.setHeader("Content-type", "application/json");
				response = httpclient.execute(httpGet);
			} else if (requestType.equals("POST")) {
				HttpPost httppost = new HttpPost(url);
				StringEntity params = new StringEntity(requestBody, HTTP.UTF_8);
				params.setContentType("application/json");
				httppost.setEntity(params);
				httppost.setHeader("Content-type", "application/json");
				response = httpclient.execute(httppost);
			}
			statusLine = response.getStatusLine();
			if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
				responseStream = response.getEntity().getContent();
			} else {
				throw new Exception("" + statusLine.getStatusCode());
			}
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new Exception("" + statusLine.getStatusCode());
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			throw new Exception("" + statusLine.getStatusCode());
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new Exception("" + statusLine.getStatusCode());
		} catch (IOException e) {
			e.printStackTrace();
			throw new Exception("" + statusLine.getStatusCode());
		}
		return responseStream;
	}

	private String makeUrl(String baseUrl, String contextPath,
			String requestBody, String contextParam)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		StringBuilder urlBuilder = new StringBuilder();
		urlBuilder.append(baseUrl);
		urlBuilder.append(contextPath);
		// urlBuilder.append(URLEncoder.encode(makeToken(requestBody)));
		// if MD5 auth is required
		System.out.println(":-) " + urlBuilder.toString());
		return urlBuilder.toString();
	}

	private String makeToken(String request) throws NoSuchAlgorithmException,
			UnsupportedEncodingException {
		String str = request + "password";
		final MessageDigest messageDigest = MessageDigest.getInstance("MD5");
		messageDigest.reset();
		messageDigest.update(str.getBytes("UTF8"));
		final byte[] resultByte = messageDigest.digest();
		BigInteger bigInt = new BigInteger(1, resultByte);
		String hashtext = bigInt.toString(16);
		// We need to zero pad it if you actually want the full 32 chars.
		while (hashtext.length() < 32) {
			hashtext = "0" + hashtext;
		}
		return hashtext;
	}
}

package com.mattiz.json.rest.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;

import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;

import org.apache.http.client.ClientProtocolException;

public class MattizRestClient {

	public static void main(String args[]) throws Exception {
		new MattizRestClient().getResponseData();
	}

	public JSONObject getResponseData() throws Exception {
		JSONObject reqObj = new JSONObject();
		JSONObject responseObj = null;
		try {
			String baseUrl = "http://localhost:8080/RestServiceExampleWithJson/restservices/";
			String contextPath = "mattiz/";
			String requestBody = reqObj.toString();
			System.out.println("requestBody--->>" + requestBody);
			String contextParam = null;
			String requestType = "GET";
			InputStream is = new JsonRestful().processRequest(baseUrl,
					contextPath, requestBody, contextParam, requestType);
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String line;
			StringBuffer sb = new StringBuffer();
			while ((line = br.readLine()) != null) {
				System.out.println("Response raw string :" + line);
				// Prints out JSON response string
				sb.append(line);
			}
			// Parse response string
			responseObj = (JSONObject) JSONSerializer.toJSON(sb.toString());
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new RuntimeException("MattizException");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			throw new RuntimeException("MattizException");
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("MattizException");
		}
		return responseObj;
	}

}

Start tomcat so that the REST service described in this post- http://cuppajavamattiz.com/2012/11/09/a-simple-restful-service/ is available with this URL –

http://localhost:8080/RestServiceExampleWithJson/restservices/mattiz

On running the main class above the following response is obtained:


requestBody—:-) >>{}
http://localhost:8080/RestServiceExampleWithJson/restservices/mattiz/
Response raw string :{“data”:”Hallo Mattiz World!”}

See snapshot below to find the jars required on the classpath.
jars

The application can be downloaded here

Short Note On Application Performance Tuning

We discuss the following basic points in this post- Heap, Heap Dumps and Thread Dumps, Memory Leaks and Connection leaks and Connection pooling
What is java heap?
When new java objects are created they are allocated memory in the java heap. Once they are no longer referenced by other java objects they may be garbage collected and memory is returned to the heap. Xms and Xmx are java switches that allow you to manipulate memory allocated to the heap.
There are two different types of heap – the new generation heap where new objects are allocated memory and the old generation heap that accommodates objects that have been in memory over a longer period. Perm memory is memory allocated to objects that have been loaded into memory through the classloader and will be removed from the perm memory only when the classloader is garbage collected.
OuOfMemory errors can be due to too many large objects in the session object in a web application and when there is not enough memory to create new objects in the java heap. This may occur when java objects are not returned back to the heap by the garbage collector. OutOfMemory exception may occur due to memory leaks. Memory leaks can be debugged by using the command:

java -verbose:gc

which will provide information on the garbage collector, objects garbage collected, etc and is used to fix garbage collector problems. A memory leak may be fixed by analyzing the logs from the above command.
Calls to Runtime.gc or Sytem.gc() should not be explicitly made. Note that threads including the JVM are paused when the major GC runs. Minor GCs do not cause the JVM to pause, but to some extent it is okay, as long it doesn’t cause noticeable slowness.
Java heap dump and thread dumps can be used to produce a stacktrace of the heap/ running threads. The stacktraces look similar to an exception stacktrace. Connection leaks are more difficult to detect since we may not know exactly where in the code a certain connection was not closed. This may be analysed by generating heap/ thread dumps using use case by use case.
Sometimes a memory leak/ application slowdown / hangup, may be difficult to find after it has been fixed. In that case one should enable GC logging / heap/ thread dumps tracing so that the next time this happens the cause is correctly found.
If a thread dump shows too many threads running it may indicate a reason for application slowdown. Thread dumps are mostly to find what the JVM is busy doing. If we see an unusually high number of threads such as more than 300 or so that means the JVM is working too hard on something and then you should look at what these threads are doing. If you see a lot of threads stuck on reading from the db that indicates db slowness. If you see too many threads servicing Servlet requests that means the JVM is overloaded with too many users.
Sometimes it helps to take thread dumps a few minutes apart and compare the two thread dumps to see if threads are moving along or stuck. If thread A in dump1 is doing the same thing in dump2 taken later, then it’s taking too long; usually you should be able to see it moving along and do different things.
It’s ok to have long running threads as some threads are by design supposed to be long lived e.g. you can have a pool of threads to consume JMS messages. Those threads will be alive for a long time and will be reused over and over again. So that’s normal as long as they don’t hold big objects for a long time, it’s not a memory leak.

Instance Variable Declaration in a Servlet vs Instance Variables Declaration in Handler classes

There is a common misunderstanding of how servlet instance variables work.
Let’s consider a Servlet, say the Controller Servlet in Struts(Action Servlet). At any given time there is only one instance of this Servlet. This Servlet is multi-threaded so it does not have state. State in the sense it has no instance variables with values. Many requests can be serviced by this Servlet by running multiple threads on the same instance.
Instance variables can be declared in a servlet but these variables are shared by all threads. No single thread can rely on using the variable for itself, so threads can never depend on the value of an instance variable of a Servlet.
This Servlet passes control to handlers say Action classes in Struts. There are multiple instances of these action classes with their own state and these action classes are not multi-threaded in that sense.
There is one multi-threaded Servlet and multiple handler classes running on different threads. One multi-threaded Servlet can create new action-class instances which are dedicated instances.

Creating a Simple Web Service using Apache Axis 1.4

Create two projects namely AxisServerExample and AxisClient example.
Download the Axis installation and add the jars in the lib folder of the installation to the lib folder of the Server project. Implement MattizAxisServerImpl.java in the server project.
MattizAxisServerImpl class has the following code:

package com.mattiz.ws.axis.server.impl;

public class MattizAxisServerImpl {
/**
 *
 * @param person
 * @return message
 */
	public String returnMessage(String person){

		return "Hallo "+person+" to the World of Axis!";
	}

}

Run the following two commands from the command prompt to use the java2wsdl utility to create the wsdl from the java class.

set PROJECT_HOME=I:/juno-ws/mvn_custom_plugin/AxisServerExample (This is the location of my axis server project)

java -cp "%PROJECT_HOME%/lib/axis-ant.jar;%PROJECT_HOME%/lib/axis.jar;%PROJECT_HOME%/lib/commons-discovery-0.2.jar;%PROJECT_HOME%/lib/commons-logging-1.0.4.jar;%PROJECT_HOME%/lib/jaxrpc.jar;%PROJECT_HOME%/lib/log4j-1.2.8.jar;%PROJECT_HOME%/lib/saaj.jar;%PROJECT_HOME%/lib/wsdl4j-1.5.1.jar;%PROJECT_HOME%/bin"  org.apache.axis.wsdl.Java2WSDL -o %PROJECT_HOME%/resources/mattiz.wsdl -l "http://localhost:8080/axis/services/MattizAxisTest" -n  "urn:mattiz" -p"com.mattiz.ws.axis.server.impl" "urn:mattiz" com.mattiz.ws.axis.server.impl.MattizAxisServerImpl

This will generate the wsdl file from MattizAxisServerImpl class in the resources folder of the Server project.

Run the following from the command prompt to use the wsdl2java utility to create the java client classes and deploy.wsdd from the wsdl file.

java -cp "%PROJECT_HOME%/lib/axis-ant.jar;%PROJECT_HOME%/lib/axis.jar;%PROJECT_HOME%/lib/commons-discovery-0.2.jar;%PROJECT_HOME%/lib/commons-logging-1.0.4.jar;%PROJECT_HOME%/lib/jaxrpc.jar;%PROJECT_HOME%/lib/log4j-1.2.8.jar;%PROJECT_HOME%/lib/saaj.jar;%PROJECT_HOME%/lib/wsdl4j-1.5.1.jar" org.apache.axis.wsdl.WSDL2Java -o %PROJECT_HOME%/src -p com.mattiz.axis.request -s %PROJECT_HOME%/resources/mattiz.wsdl

deploy.wsdd is created in com.mattiz.axis.request folder along with web serice client stub classes in the Server project.

Copy contents of axis installation webapps folder to tomcat. Modify web.xml by uncommenting this line:

 <!--
  <servlet-mapping>
    <servlet-name>AdminServlet</servlet-name>
    <url-pattern>/servlet/AdminServlet</url-pattern>
  </servlet-mapping>
 -->

Start tomcat and run the following from the command prompt to use the Apache Admin Client to generate your server-config file (wsdd).

java -cp "%PROJECT_HOME%/lib/axis-ant.jar;%PROJECT_HOME%/lib/axis.jar;%PROJECT_HOME%/lib/commons-discovery-0.2.jar;%PROJECT_HOME%/lib/commons-logging-1.0.4.jar;%PROJECT_HOME%/lib/jaxrpc.jar;%PROJECT_HOME%/lib/log4j-1.2.8.jar;%PROJECT_HOME%/lib/saaj.jar;%PROJECT_HOME%/lib/wsdl4j-1.5.1.jar" org.apache.axis.client.AdminClient -lhttp://localhost:8080/axis/services/AdminService %PROJECT_HOME%/src/com/mattiz/axis/request/deploy.wsdd

server-config.wsdd is created in the axis deployment in tomcat in the WEB-INF folder. Copy this to resources folder under Server project.

You are free to delete the axis installation in webapps of tomcat after this step.

Copy web.xml from the axis distribution at axis-1_4/webapps/axis/WEB-INF to WEB-INF under Server project. No changes are required to this file.
Modify returnMessage method in AxisServerExample/src/com/mattiz/axis/request/MattizAxisTestSoapBindingImpl.java

which was generated with the stubs, to look like this:

    public java.lang.String returnMessage(java.lang.String person) throws java.rmi.RemoteException {
        return new MattizAxisServerImpl().returnMessage(person);
    }

Run the following build.xml which creates the server side web service war file – mattiz.war.

<?xml version="1.0"?>
<project name="mattiz" default="war" basedir=".">
	<path id="project.class.path">
		<fileset dir="lib">
			<include name="*.jar" />
		</fileset>
	</path>
	<target name="clean">
		 <delete failonerror="true" includeemptydirs="true">
            <fileset dir="target/classes" includes="**/*" />
            <fileset dir="target" includes="**/*" />
        </delete>
	</target>
	<target name="compile" depends="clean">
		<mkdir dir="target" />
		<mkdir dir="target/classes" />
		<javac srcdir="src" destdir="target/classes" optimize="off"
			classpathref="project.class.path" />
	</target>
	<target name="war" depends="compile">
		<copy file="resources/server-config.wsdd" todir="target/classes" />
		<war destfile="target/mattiz.war" webxml="WEB-INF/web.xml">
			<classes dir="target/classes">
				<include name="**/*.*"/>
			</classes>
			<lib dir="lib">
				<include name="**/*.jar" />
			</lib>
		</war>
	</target>
</project>

Copy mattiz.war to tomcat and start tomcat server.

Run the following url on a browser to view the wsdl file. If the web service is successfully created you will see a xml version of the wsdl file.

http://localhost:8080/mattiz/services/MattizAxisTest?wsdl

Copy contents of com.mattiz.axis.request package to the Client project. Exclude MattizAxisTestSoapBindingImpl class.

Add activation.jar and javamail jars to the classpath of Client project in addition to the axis jars in Server project’s lib folder.

Create MattizWebServiceConsumer.java thus:

package com.mattiz.axis.request;

import java.rmi.RemoteException;
import javax.xml.rpc.ServiceException;

public class MattizWebServiceConsumer {

	public static void main(String[] args) {
		try {
			String endpoint = "http://localhost:8080/mattiz/services/MattizAxisTest";

			MattizAxisServerImplServiceLocator srvLoc = new MattizAxisServerImplServiceLocator();
			srvLoc.setEndpointAddress(new javax.xml.namespace.QName(
					"urn:mattiz", "MattizAxisTest"), endpoint);
			MattizAxisServerImpl serviceimpl = srvLoc.getMattizAxisTest();

			((javax.xml.rpc.Stub) serviceimpl)._setProperty(
					"javax.xml.rpc.session.maintain", Boolean.TRUE);
			String person = "Matty";

			String ret = serviceimpl.returnMessage(person);
			System.out.println("Message " + ret);

		} catch (ServiceException e) {
			// TODO Auto-generated catch blockS
			e.printStackTrace();
		} catch (RemoteException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

Run MattizWebServiceConsumer.java as standalone.

You see the following messsage on the console when the web service java client calls the server:

Message Hallo Matty to the World of Axis!
client

server

The Axis server example can be downloaded here here
The Axis client example can be downloaded here

Custom Maven Plugin To Investigate Contents of Zip File

Run the following command on command line at the root of your workspace folder

mvn archetype:create -DgroupId=com.mattiz.maven.plugins -DartifactId=zipped-file-content-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo

It will create a default pom.xml similar to this in a folder called zipped-file-content-plugin:

<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>com.mattiz.maven.plugins</groupId>
  <artifactId>zipped-file-content-plugin</artifactId>
  <packaging>maven-plugin</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>zipped-file-content-plugin Maven Mojo</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>org.apache.maven</groupId>
      <artifactId>maven-plugin-api</artifactId>
      <version>2.0</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

For our purpose we don’t need to add any other dependencies. This may not be the case for complex functionalities such as parsing an xml file.

Goto MattizCustomPlugin/zipped-file-content-plugin folder on command line and run mvn eclipse:eclipse. This will create an eclipse project for you. Import the eclipse project into a new workspace.

A java file called MyMojo.java is created under src/main/java/com/mattiz/mavenplugins. Rename it to MattizZipDetectivePlugin.java and modify the contents to look like this:

package com.mattiz.maven.plugins;

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * @goal explore
 *
 * @phase process-sources
 */
public class MattizZipDetectivePlugin extends AbstractMojo {
	/**
	 * Location of the file.
	 *
	 * @parameter expression="${project.build.directory}"
	 * @required
	 */
	public void execute() throws MojoExecutionException {
		try {
			listFiles(new File(
					"."+File.separator+"package_holder"+File.separator+"Mattiz.zip"));
		} catch (ZipException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void listFiles(File zippedFile) throws ZipException, IOException {
		ZipFile zipFile = new ZipFile(zippedFile);
		try {
			Enumeration<? extends ZipEntry> entries = zipFile.entries();
			while (entries.hasMoreElements()) {
				ZipEntry entry = entries.nextElement();
				File file = new File(entry.getName());
				System.out.println(file.getCanonicalPath());
			}
		} finally {
			zipFile.close();
		}
	}
}

Note this change:

@goal explore
This defines the plugin goal.

Go to folder where the pom file lies, on command prompt and run
mvn install. This creates the snapshot jar containing your custom maven plugin here:
zipped-file-content-plugin/target/zipped-file-content-plugin-1.0-SNAPSHOT.jar

Create a package called package_holder under root of project and place a zip file with arbitrary content called Mattiz.zip here.

Run the following command from the command prompt.

mvn com.mattiz.maven.plugins:zipped-file-content-plugin:1.0-SNAPSHOT:explore

The command line will list the contents of Mattiz.zip.
custom-maven

The source code can be downloaded here