Monday, September 29, 2008

Axis2 web service muddle through

Step1 Install Axis2
go to http://ws.apache.org/axis2 to download a binary version (I'm using 1.4.1 Standard Binary Distribution). After successfully installment, you can get the following screen after Axis2 started.

Step2: Define web service
Create a new Java project inside eclipse. Its structure should look like the picture below. I named my project VINWebService which return some dumb information of vehicles.

In my example I defined three web services:
1. findMakes: return car makes for a given year
2. findModels: return models for a given car make
3. findBodyTypes: return body types for a given model

the source code is:
package tao.test.web.vin;


import java.util.HashMap;
import java.util.Map;

public class VINService {
private static Map makeData = new HashMap();
private static Map modelData = new HashMap();
private static Map bodyTypeData = new HashMap();

static {
String[] makes_08 = {"CLK-CLASS", "CLS-CLASS", "E-CLASS", "GL-CLASS"};
makeData.put(2008L, makes_08);
}

public String[] findBodyTypes(String modelCode) {
return bodyTypeData.get(modelCode);
}

public String[] findMakes(long year) {
return makeData.get(year);
}

public String[] findModels(String makeCode) {
return modelData.get(makeCode);
}

}
Though there are rumors that the newest Axis2 version supports Java Collection as the return type now, I still got ADBException due to "any type element type has not been given". It seems Array is the safest choice.

Step3: Prepare service.xml
The easiest way of preparing service.xml is to modify a current one. My file is original from that Stock Quote sample, which comes with the Axis2 binary. Only those orange marked fields need to be changed based on different service.
<service name="VINService" scope="application" targetNamespace="http://tao.vinservice/">


<description>

VIN Query Service

</description>

<messageReceivers>

<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only"

class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver"/>

<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"

class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>

</messageReceivers>

<schema schemaNamespace="
http://tao.vinservice/xsd"/>

<parameter name="ServiceClass">
tao.test.web.vin.VINService</parameter>

</service>
Step 4: Deploy Web Service
I tweaked the ant.build file of the stock quote example and extracted project-specified field out into the properties file. After changing those property values, you can build the web service directly if the project's structure remains same as the first picture.

Ant build.property
axis2.home=C:/Tao/software/axis2-1.4.1 #Where you installed your Axis2

aar.file.name=VINService.aar #The aar file name
service.class.name=tao.test.web.vin.VINService #Service Java class name, keep it same with the service.xml
service.target.namespace=http://tao.vinservice/ #Name space, keep it same with the service.xml
service.schema.target.namespace=http://tao.vinservice/xsd #Schema name space, keep it same with the service.xml
Ant.xml

<?xml version="1.0" encoding="UTF-8"?>



<project name="VINWebService" basedir="." default="generate.service">

<property environment="env"/>

<property file="build.properties"/>

<property name="build.dir" value="build"/>

<path id="axis2.classpath">

<fileset dir="${axis2.home}/lib">

<include name="*.jar"/>

</fileset>

</path>

<target name="clean" description="Remove all build files">

<delete dir="${build.dir}" includeEmptyDirs="true"/>

</target>

<target name="compile.service">

<mkdir dir="${build.dir}"/>

<mkdir dir="${build.dir}/classes"/>

<!--First let's compile the classes-->

<javac debug="on" fork="true" destdir="${build.dir}/classes" srcdir="${basedir}/src" classpathref="axis2.classpath" target="1.5"/>

</target>

<target name="generate.wsdl" depends="compile.service">

<taskdef name="java2wsdl" classname="org.apache.ws.java2wsdl.Java2WSDLTask" classpathref="axis2.classpath"/>

<java2wsdl className="${service.class.name}" outputLocation="${build.dir}" targetNamespace="${service.target.namespace}" schemaTargetNamespace="${service.schema.target.namespace}">

<classpath>

<pathelement path="${axis2.classpath}"/>

<pathelement location="${build.dir}/classes"/>

</classpath>

</java2wsdl>

</target>


<target name="generate.service" depends="compile.service">

<!--aar them up -->

<copy toDir="${build.dir}/classes" failonerror="false">

<fileset dir="${basedir}/resources">

<include name="**/*.xml"/>

</fileset>

</copy>

<jar destfile="${build.dir}/${aar.file.name}">

<fileset excludes="**/Test.class" dir="${build.dir}/classes"/>

</jar>

</target>

</project>
Run ant build, VINService.aar will be created. Droping this file into the services fold of your Axis2 home, you can see the new service VINService is ready for use.

Step 5: Test
Using WSDL2Java script come with Axis2 binary, you can create a test client at a drop of a hat. In my case, just run
WSDL2Java.bat -uri http://localhost:8080/axis2/services/VINService?wsdl -o .
two java file VINServiceCallbackHandler.java and VINServiceStub.java will be generated. Those two files will be grouped under package vinservice.tao which we defined as our service target namespace.
Create a new java class named client and add the following data:
package vinservice.tao;


import vinservice.tao.VINServiceStub.FindMakes;
import vinservice.tao.VINServiceStub.FindMakesResponse;

public class Client {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
VINServiceStub stub = new VINServiceStub();

FindMakes request = new VINServiceStub.FindMakes();
request.setYear(2008L);

FindMakesResponse response = stub.findMakes(request);

String[] rs = (String[])response.get_return();

for (String s : rs) {
System.out.println(s);
}
}
}
Run this client, the console will print out our the makes we have in the server side.


Example source code:

Thursday, September 25, 2008

Retrieve EntityManager while using Spring JpaTemplate

In current project, I'm using Spring framework combine with OpenJpa. Life seems becoming much easier while using Spring JpaTemplate to handle all the database queries. However those methods provide by JpaTemplate class are not powerful enough to satisfy all kinds of requirements, so we may need use EntityManager to prepare query at some point.

When I was facing the problem of retrieve only the first n rows of a query, I could not find any method in JpaTemplate would that help me easily achieve this goal. At insult to the injury, there is no such keyword in JPQL like the rownum in Oracle or limit in Mysql that we can use directly into the query -- there may do have a keyword, but I didn't find by google. The only choice I have is to do it using Hibernate way, using setFirstResult() and setMaxResults() methods. In that case we do need an instance of EntityManager.

JpaTemplate class has a public method called getEntityManager(). The interesting fact is this mehod will always return null. Based on the online gossips of some people, though the getEntityManager's indentifier is public, no one should call it explicitedly. It can only be used by Spring itself. After further googled this issue, there are many ways to workaround this. Among all those solutions, using JpaCallback may be the simplest one.

There's some sample code:

List results = getJpaTemplate().executeFind(
new JpaCallBack() {
public Object doInJpa(EntityManager em) throws PersistenceException {
Query query = em.createQuery(queryStr);
query.setFirstResult(0);
query.setMaxResults(1000);
List results = query.getResultList();
return results;
}
}
);

Friday, September 19, 2008

Export Excel file under JSF

I think maybe it is a good idea to write down how to export Excel file under JSF. I'm using Myfaces 1.2 and Apache POI 3.1

The basic code should look like:
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response =
(HttpServletResponse)context.getExternalContext().getResponse();
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition","attachment;filename=job.xls");
response.setHeader("Pragma", "no-cache");
try{
OutputStream os = response.getOutputStream();

int rowIndex = 0;

// Create Excel Object
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("sheet 1");

// Print out configuration, optional
HSSFPrintSetup ps = sheet.getPrintSetup();
ps.setFitWidth((short)1);

// Define cell style, optional
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setWrapText(true);

// Create a row
HSSFRow row = sheet.createRow(rowIndex++);
// Create cells
HSSFCell cell = row.createCell((short)0);
cell.setCellValue(new HSSFRichTextString("Id") );
cell.setCellStyle(cellStyle);
cell = row.createCell((short)1);
cell.setCellStyle(cellStyle);
cell.setCellValue(new HSSFRichTextString("Name") );
cell = row.createCell((short)2);
cell.setCellStyle(cellStyle);
cell.setCellValue(new HSSFRichTextString("Email") );
cell.setCellStyle(cellStyle);
cell = row.createCell((short)3);
cell.setCellValue(new HSSFRichTextString("Phone Number") );
cell.setCellStyle(cellStyle);
// Your own code ...

wb.write(ops);
os.flush();
os.close();
}
catch(IOException ioe) {
// Handle exception ...
}

FacesContext.getCurrentInstance().responseComplete();

Tell the truth, the default setting of POI is not very smart. In real project you may need add some extra code to beautify your excel. I list some useful settings here.

Print Configuration
Fit your Excel the width of one page
HSSFPrintSetup ps = sheet.getPrintSetup();
ps.setFitWidth((short)1);
Set default orientation to landscape
HSSFPrintSetup ps = sheet.getPrintSetup();
ps.setLandscape(true);
Auto break
sheet.setAutobreaks(true);

Cell Configuration
Auto wrap text
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setWrapText(true);
Alignment
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setAlignment(HSSFCellStyle.ALIGN_LEFT);
Vertical Alignment
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_TOP);
You can get more detail information of POI by checking out the following link:
http://poi.apache.org/hssf/quick-guide.html

Thursday, September 18, 2008

Back slashes needed to escape a quote in Java

During today's work, I faced the problem of escaping the quote, double quote and line break inside a String while sending it into javascript.

The following code was the approach first came into my mind:

String eStr = orginalStr.replaceAll("\"", "\\\"");
eStr = eStr.replaceAll("'", "\\'");
eStr = eStr.replaceAll("\\r\\n", "<br>");

Ironically, none single line of these code works. After some research, I figured out the correct way should be:

String eStr = orginalStr.replaceAll("\"", "\\\\\"");
eStr = eStr.replaceAll("'", "\\\\'");
eStr = eStr.replaceAll("\r\n", "<br>");

It seems my previous code replace a quote by quote itself. The interesting result is the following two lines are quite the same:

String eStr = orginalStr.replaceAll("\"", "\\\"");
String eStr = orginalStr.replaceAll("\"", "\"");

So four more back slashes are required to escape the quote or single quote. However six back slashes are needed to escape back slash itself.