Friday, February 22, 2013

Spring AOP Logging Example

In general, we print the input arguments and output response for debugging. If we add log statements in each methods, it is very troublesome, spring provides AOP make it easy.

1) add this configuration

<aop:aspectj-autoproxy />

2)


@Component
@Aspect
public class ServiceLoggingAspect extends ServiceBase {

@Around("execution(* com.abc.service.impl.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {

Long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
info("[" + joinPoint.getTarget().getClass().getName() + "]."
+ joinPoint.getSignature().getName(),
" return " + result + " with arguments : "
+ Arrays.toString(joinPoint.getArgs())
+ ", Time to execute : "
+ (System.currentTimeMillis() - startTime) + "ms");
return result;
} catch (Exception e) {
error("[" + joinPoint.getTarget().getClass().getName() + "]."
+ joinPoint.getSignature().getName(),
"Exception occured with arguments : "
+ Arrays.toString(joinPoint.getArgs())
+ ", Time to execute : "
+ (System.currentTimeMillis() - startTime) + "ms",
e);

throw e;
}
}
}

Take note: The runtime exception will not be printed, but checked exception will be printed.

Spring Transaction Rollback


  • transaction rollback testing
1) class in DAO layer


public interface AccountDao {

public void addAccount(Account account)  ;

}


@Repository("accountDao")
public class AccountDaoImpl  implements AccountDao {

@Autowired
        private SessionFactory sessionFactory;

public void addAccount(Account account) {
sessionFactory.getCurrentSession().save(account);

}

}

2) class in service layer


@Transactional(rollbackFor=DBException.class)
public interface AccountService {
public void addAccount(Account account) throws DBException;
}


DBException is a self-defined checked exception.

Only unchecked exceptions (that is, subclasses of java.lang.RuntimeException) are rollbacked by default in spring. If want to roll back for checked exception, need to declare by this way @Transactional(rollbackFor=DBException.class)

@Service("accountService")
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountDao accountDao;

public void addAccount(Account account) throws DBException{
try {
accountDao.addAccount(account);
} catch (Exception e) {
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}

}

3) junit test



@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:spring-context-service-test.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class AccountServiceTester {

@Autowired
AccountService accountService;

@Before
public void setUp() throws Exception {
}

@After
public void tearDown() throws Exception {
}


@Test
@Rollback(false)
public void testAccount2() throws DBException{

Account account=new Account();
account.setUserid("0011abc22");
account.setPassword("111");
account.setTimeStamp(new Date());
accountService.addAccount(account);
Account account2=new Account();
account2.setUserid("0011abc22");
account2.setPassword("222");
account2.setTimeStamp(new Date());
accountService.addAccount(account2);
}

@Test
@Rollback(false)
public void testAccount3() throws DBException{

Account account=new Account();
account.setUserid("0011abc4");
account.setPassword("111");
account.setTimeStamp(new Date());
accountService.addAccount(account);

}

@Test
@Rollback(false)
public void testAccount4() throws DBException{

Account account=new Account();
account.setUserid("0011abc4");
account.setPassword("222");
account.setTimeStamp(new Date());
accountService.addAccount(account);

}
}

@Rollback(false) means the data will be committed into database, otherwise spring will rollback all DB operations.

userId is the private key in DB.

Test Result:
a) The data in method testAccount2()  will not be committed to database because the two accountService.addAccount(account) share the same DB transaction and DB will rollback due to duplicate key error
b) The data in method  testAccount3() will be committed to database as it is a standalone DB transaction
c) The data in method  testAccount4() will not be committed to database due to duplicate key error even it is a standalone DB transaction



  • how to use
public void serviceMethod(){

dao.method1();
....
...
// do something
...
...
dao.method2();

}

It is not good to write the source code if dao.method1() and dao.method2() do NOT share the same DB transaction. If writing code as above, the two dao methods will share the same DB transaction.
what we can do is to write a private method to call the two dao methods individually.

Based on the description in http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html


PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW, in contrast to PROPAGATION_REQUIRED, uses a completely independent transaction for each affected transaction scope. In that case, the underlying physical transactions are different and hence can commit or roll back independently, with an outer transaction not affected by an inner transaction's rollback status.


in the interface, we have the two methods:


public void addAccount2() throws DBException;
public void addAccount3() throws DBException;

in the implementation class,


@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=DBException.class)

public void addAccount2() throws DBException{
Account account=new Account();
account.setUserid("ccc444");
account.setPassword("111");
account.setTimeStamp(new Date());
try{
accountDao.addAccount(account);
}catch(Exception e){
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}
@Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=DBException.class)

public void addAccount3() throws DBException{
Account account=new Account();
account.setUserid("ccc444");
account.setPassword("222");
account.setTimeStamp(new Date());
try{
accountDao.addAccount(account);
}catch(Exception e){
e.printStackTrace();
throw new DBException(DBException.ERR_ADD_ACCOUNTS,
e);
}
}

in the junit class,


@Test
@Rollback(false) public void testAccount5() throws Exception{
     accountService.addAccount2();
     accountService.addAccount3();
 }

then the two methods have their own DB transactions.


Wednesday, February 20, 2013

Add Total Row in jQuery DataTables

1) json response written by spring mvc 3


      @RequestMapping(value = "/getTestData", produces = "application/json")
public @ResponseBody List<TestData> getTestDataTable() {

List<TestData> result = new ArrayList<TestData>();
TestData d1 = new TestData();
d1.setName("Hello A");
d1.setNuma(1100);
d1.setNumb(7);

TestData d2 = new TestData();
d2.setName("B hello");
d2.setNuma(3412);
d2.setNumb(5);
result.add(d1);
result.add(d2);

return result;
}


This is the response sample:

[{"name":"Hello A","numa":1100,"numb":7},{"name":"B hello","numa":3412,"numb":5}]


2) add a table in html


<table id="id_dt_test" class="display">
<thead>
<tr>
<th width="20%">Name</th>
<th width="20%">Number A</th>
<th width="20%">Number B</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<th style="text-align: right">Total:</th>
<th style="text-align: left"></th>
<th style="text-align: left"></th>
</tr>
</tfoot>
</table>

3)  init datatables


$('#id_dt_test').dataTable({
"bProcessing" : true,
"bDestroy" : true,
"sAjaxSource" : 'getTestData.do',
"sAjaxDataProp" : "",
"bFilter" : false,
"bInfo" : false,
"bLengthChange" : false,
"aoColumns" : [ {
"mData" : "name"
}, {
"mData" : "numa",
}, {
"mData" : "numb"
} ],
"bPaginate" : false,
"bAutoWidth" : false,
"fnFooterCallback" : function(nRow, aaData, iStart, iEnd,
aiDisplay) {
var iTotalNuma = 0;
var iTotalNumb = 0;
if (aaData.length > 0) {
for ( var i = 0; i < aaData.length; i++) {
iTotalNuma += aaData[i].numa;
iTotalNumb += aaData[i].numb;
}
}
/*
* render the total row in table footer
*/
var nCells = nRow.getElementsByTagName('th');
nCells[1].innerHTML = iTotalNuma;
nCells[2].innerHTML = iTotalNumb;

}
});



4) actual result:




Reference URL:
http://datatables.net/release-datatables/examples/advanced_init/footer_callback.html

Wednesday, February 6, 2013

css/js files are locked if running in jetty

css/js files are not allowed to edit if running in maven jetty plugin, so it is very troublesome to restart jetty if need to edit css/js files.


  • why
refer to http://docs.codehaus.org/display/JETTY/Files+locked+on+Windows
  • solution
need to change useFileMappedBuffer to false in webdefault.xml


<param-name>useFileMappedBuffer</param-name>
<param-value>true</param-value> <!-- change to false -->

if you are using maven jetty plugin in eclipse, how to find the file webdefault.xml

refer to http://wiki.eclipse.org/Jetty/Feature/Jetty_Maven_Plugin

for the plugin, the file webdefault.xml in jar file jetty-webapp.jar

so go to %user%\.m2\repository\org\eclipse\jetty\jetty-webapp\{jetty version}\jetty-webapp-{jetty version}.jar

open org/mortbay/jetty/webapp/webdefault.xml and change the configuration mentioned above

Tuesday, February 5, 2013

Spring and slf4j issue


2013-02-05 13:44:05.963:WARN:oejuc.AbstractLifeCycle:FAILED org.mortbay.jetty.plugin.JettyServer@1ccd159: java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Throwable;)V
java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log(Lorg/slf4j/Marker;Ljava/lang/String;ILjava/lang/String;Ljava/lang/Throwable;)V
at org.apache.commons.logging.impl.SLF4JLocationAwareLog.info(SLF4JLocationAwareLog.java:159)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:272)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:733)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:233)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1222)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:676)


checked the source code, by default spring use commons.logging to write log, but slf4j is being used in the project, so need to use another logging system for spring to use, such as jcl-over-slf4j

to resolve this problem, need to exclude commons logging from spring and replace with jcl-over-slf4j

<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.2</version>
</dependency> 
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
                <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions> 
</dependency>

Spring MVC 3 + Tiles

Env: spring mvc v3.2
        tiles: v2.2.2

1) add tiles dependencies to the pom


<!-- apache tiles -->
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-core</artifactId>
<version>${tiles.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-template</artifactId>
<version>${tiles.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-jsp</artifactId>
<version>${tiles.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-servlet</artifactId>
<version>${tiles.version}</version>
</dependency>
<!-- End apache tiles -->

2) configure view class and tiles definitions file



<bean class="org.springframework.web.servlet.view.tiles2.TilesViewResolver"/>
 
 <bean id="tilesConfigurer"
class="org.springframework.web.servlet.view.tiles2.TilesConfigurer"
p:definitions="classpath:tiles-defs.xml" />

3) tiles-defs.xml


<!DOCTYPE tiles-definitions PUBLIC "-//Apache Software Foundation//DTD Tiles Configuration 2.1//EN" "http://tiles.apache.org/dtds/tiles-config_2_1.dtd">
<tiles-definitions>
<definition name="baselayout" template="/view/jsp/baseLayout.jsp">
<put-attribute name="header" value="" />
<put-attribute name="body" value="" />
<put-attribute name="footer" value="/view/jsp/footer.jsp" />
</definition>

<definition name="summary" extends="baselayout">

</definition>

<!-- abc page -->
<definition name="abc.do" extends="baselayout">
<put-attribute name="desc" value="abc" />
<put-attribute name="title" value="abc" />
<put-attribute name="header" value="/view/jsp/header.jsp" />
<put-attribute name="body" value="/view/abc.jsp" />
</definition>

</tiles-definitions>




4) add a controller to handler url /abc.do

Monday, February 4, 2013

Spring Cache

1) add the following configurations to support cache

refer to : http://static.springsource.org/spring/docs/3.1.0.M1/spring-framework-reference/html/cache.html


<cache:annotation-driven cache-manager="cacheManager" />
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cache-manager-ref="ehcache" />
         <!-- use ehcache here  -->
<bean id="ehcache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:config-location="classpath:ehcache.xml" />

2) ehcache.xml   (refer to http://ehcache.org/documentation )


<ehcache>
<diskStore path="java.io.tmpdir" />

<cache name="userCache" maxElementsInMemory="100" eternal="false"
timeToIdleSeconds="60" timeToLiveSeconds="60" overflowToDisk="false"
maxElementsOnDisk="10000000" />

</ehcache>

it is configured to refresh the content of cache per 60 seconds

3) add annotation @Cacheable(value = "userCache") to enable cache


@Transactional
public interface AccountService {

@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
@Cacheable(value = "userCache")
public List<Account> getAccounts();
@Cacheable(value = "userCache")
public List<Account> getAccounts(int currPageNo,int pageSize);

@CacheEvict (value = "userCache", allEntries=true)
public void addAccount(String userId,String password);

@CacheEvict (value = "userCache", allEntries=true)
public void addAccounts(List<Account> accounts) throws DataAccessException ;

@CacheEvict (value = "userCache", allEntries=true)
public void updateAccount(Account account);
@Cacheable(value = "userCache")
public Account getAccount(String userId);
@CacheEvict (value = "userCache", allEntries=true)
public void deleteAccount(String userId);
@CacheEvict (value = "userCache", allEntries=true)
public void updateAccount(String userId,int status);
}

4) add maven dependency
refer to : http://code.google.com/p/ehcache-spring-annotations/

<dependency>
  <groupId>com.googlecode.ehcache-spring-annotations</groupId>
  <artifactId>ehcache-spring-annotations</artifactId>
  <version>1.2.0</version>
</dependency>

Develop web service with jaxws - print request/response SOAP messages

1) add chain configuration file LogMessage_handler.xml under folder WEB-INF\classes


<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
  <handler-chain>
    <handler>
      <handler-name>com.abc.ws.demows.skeleton.LogSoapMessageHandler</handler-name>
     <handler-class>com.abc.ws.demows.skeleton.LogSoapMessageHandler</handler-class>
    </handler>
  </handler-chain>
</handler-chains>


2) add above configured class LogSoapMessageHandler


package com.abc.ws.demows.skeleton;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.LogicalMessage;
import javax.xml.ws.handler.LogicalHandler;
import javax.xml.ws.handler.LogicalMessageContext;
import javax.xml.ws.handler.MessageContext;

public class LogSoapMessageHandler implements
LogicalHandler<LogicalMessageContext> {

public void close(MessageContext msgCtx) {

}

public boolean handleFault(LogicalMessageContext lmc) {

return false;
}

public boolean handleMessage(LogicalMessageContext context) {
boolean direction = ((Boolean) context
.get(LogicalMessageContext.MESSAGE_OUTBOUND_PROPERTY))
.booleanValue();

try {
LogicalMessage lm = ((LogicalMessageContext) context).getMessage();
if (lm != null) {
Source source = lm.getPayload();
if (source != null) {
System.out
.println(direction == true ? "Response SOAP Message : \n"
+ getSourceAsString(source)
: "Request SOAP Message : \n"
+ getSourceAsString(source));

} else {
System.out.println(" No Message payload was present");
}
} else {
System.out.println("\n No Message was present");
}
} catch (Exception e) {
System.out.println("Exception while dumping soap message." + e);
}

return true;
}

private String getSourceAsString(Source s) throws Exception {

Transformer transformer = TransformerFactory.newInstance()
.newTransformer();

OutputStream out = new ByteArrayOutputStream();
StreamResult streamResult = new StreamResult();
streamResult.setOutputStream(out);
transformer.transform(s, streamResult);

return streamResult.getOutputStream().toString();
}

}


3) add above class to skeleton class

@WebService( endpointInterface = "com.abc.ws.demows.DemoService" )
@HandlerChain( file = "LogMessage_handler.xml" )
public class DemoServiceImpl implements DemoService {
...
}


4) test result

Request SOAP Message :
<?xml version="1.0" encoding="UTF-8"?><createReqParams xmlns="http://ws.abc.com/demows/" xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://ws.abc.com/common/"><header><ns2:clientId
>CID0001</ns2:clientId><ns2:timeStamp>2013-02-04T12:46:36.693+08:00</ns2:timeStamp></header><prodId>PID02</prodId></createReqParams>

request data: prodId=PID02,clientId=CID0001,timestamp=Mon Feb 04 12:46:36 SGT 2013

Response SOAP Message :
<?xml version="1.0" encoding="UTF-8"?><createRespParams xmlns="http://ws.abc.com/demows/" xmlns:ns2="http://ws.abc.com/common/"><header><ns2:returnCode>9999</ns2:returnCode><ns2:returnMessage>OK</ns2:
returnMessage></header><prodId>PID01</prodId><prodStatus>Pass</prodStatus></createRespParams>


Develop web service with jaxws


1) download jax-ws jar from either http://jax-ws.java.net or maven respository
2) add JAXWS_HOME to your system env variables
3) key in command
wsgen -version
or
wsimport -version
to verify jax-ws setup is ok

wsgen generates the necessary artifacts required for jax-ws applications when starting from Java code.
wsimport processes an existing WSDL file and generates the required artifacts for developing jax-ws web service applications.

4) start from wsdl

==== define a soap header in a separate xsd file common-schema.xsd ==========

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:common="http://ws.abc.com/common/" attributeFormDefault="unqualified"
elementFormDefault="qualified" targetNamespace="http://ws.abc.com/common/">
<xs:complexType name="reqHeader">
<xs:sequence>
<xs:element name="clientId" type="xs:string" />
<xs:element name="timeStamp" type="xs:dateTime" />
</xs:sequence>
</xs:complexType>
<xs:complexType name="respHeader">
<xs:sequence>
<xs:element name="returnCode" type="common:codeType" />
<xs:element name="returnMessage" type="xs:string" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="codeType">
<xs:restriction base="xs:string">
<xs:enumeration value="000" />
<xs:enumeration value="999" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

===============  define demo.wsdl ======================



<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="demoService" targetNamespace="http://ws.abc.com/demows/"
xmlns:common="http://ws.abc.com/common/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:demows="http://ws.abc.com/demows/" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:types>
<xs:schema>
<xs:import namespace="http://ws.abc.com/demows/"
schemaLocation="demo-schema.xsd" />
<xs:import namespace="http://ws.abc.com/common/"
schemaLocation="common-schema.xsd" />
</xs:schema>
</wsdl:types>

<wsdl:message name="reqCreate">
<wsdl:part name="parameters" element="demows:createReqParams" />
</wsdl:message>
<wsdl:message name="respCreate">
<wsdl:part name="parameters" element="demows:createRespParams" />
</wsdl:message>


<wsdl:message name="reqGet">
<wsdl:part name="parameters" element="demows:getReqParams" />
</wsdl:message>
<wsdl:message name="respGet">
<wsdl:part name="parameters" element="demows:getRespParams" />
</wsdl:message>

<wsdl:portType name="DemoService">
<wsdl:operation name="create" parameterOrder="parameters">
<wsdl:input message="demows:reqCreate" />
<wsdl:output message="demows:respCreate" />
</wsdl:operation>

<wsdl:operation name="get" parameterOrder="parameters">
<wsdl:input message="demows:reqGet" />
<wsdl:output message="demows:respGet" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="DemoServicePortBinding" type="demows:DemoService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="create">
<soap:operation soapAction="create" />
<wsdl:input>
<soap:body parts="parameters" use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body parts="parameters" use="literal" />
</wsdl:output>
</wsdl:operation>

<wsdl:operation name="get">
<soap:operation soapAction="get" />
<wsdl:input>
<soap:body parts="parameters" use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body parts="parameters" use="literal" />
</wsdl:output>
</wsdl:operation>

</wsdl:binding>
<wsdl:service name="DemoService">
<wsdl:port name="DemoServicePort" binding="demows:DemoServicePortBinding">
<soap:address location="http://localhost:8080/demows/demoservice" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>



================ xsd file (demo-schema.xsd) for demo.wsdl   ============

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:common="http://ws.abc.com/common/" xmlns:demows="http://ws.abc.com/demows/"
attributeFormDefault="unqualified" elementFormDefault="qualified"
targetNamespace="http://ws.abc.com/demows/">
<xs:import namespace="http://ws.abc.com/common/"
schemaLocation="common-schema.xsd" />
<xs:element name="createReqParams" type="demows:createReqParamsType" />
<xs:element name="createRespParams" type="demows:createRespParamsType" />
<xs:element name="getReqParams" type="demows:getReqParamsType" />
<xs:element name="getRespParams" type="demows:getRespParamsType" />

<xs:complexType name="createReqParamsType">
<xs:sequence>
<xs:element name="header" type="common:reqHeader" />
<xs:element name="prodId" type="xs:string" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="createRespParamsType">
<xs:sequence>
<xs:element name="header" type="common:respHeader" />
<xs:element name="prodId" type="xs:string" />
<xs:element name="prodStatus" type="demows:prodStatus" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="getReqParamsType">
<xs:sequence>
<xs:element name="header" type="common:reqHeader" />
<xs:element name="prodId" type="xs:string" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="getRespParamsType">
<xs:sequence>
<xs:element name="header" type="common:respHeader" />
<xs:element name="prodId" type="xs:string" />
<xs:element name="prodList" type="demows:prodList" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="prodList">
<xs:sequence>
<xs:element name="prodDetail" type="demows:prodDetail"
maxOccurs="unbounded" minOccurs="0" />
</xs:sequence>
</xs:complexType>

<xs:complexType name="prodDetail">
<xs:sequence>
<xs:element name="prodId" type="xs:string" />
<xs:element name="prodName" type="xs:string" />
<xs:element minOccurs="0" name="prodType" type="demows:prodType" />
</xs:sequence>
</xs:complexType>

<xs:simpleType name="prodStatus">
<xs:restriction base="xs:string">
<xs:enumeration value="Pass" />
<xs:enumeration value="Fail" />
<xs:enumeration value="Pending" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="prodType">
<xs:restriction base="xs:string">
<xs:enumeration value="Good" />
<xs:enumeration value="Bad" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

5) use wsimport to generate skeleton stuff

wsimport demo.wsdl -Xnocompile -extension

6) skeleton code:


package com.abc.ws.demows.skeleton;

import java.util.Date;

import javax.jws.WebService;
import javax.xml.ws.Holder;

import com.abc.ws.common.ReqHeader;
import com.abc.ws.common.RespHeader;
import com.abc.ws.demows.CreateReqParamsType;
import com.abc.ws.demows.CreateRespParamsType;
import com.abc.ws.demows.DemoService;
import com.abc.ws.demows.GetReqParamsType;
import com.abc.ws.demows.GetRespParamsType;
import com.abc.ws.demows.ProdStatus;
@WebService( endpointInterface = "com.abc.ws.demows.DemoService" )
public class DemoServiceImpl implements DemoService {

@Override
public void create(CreateReqParamsType parameters,
Holder<CreateRespParamsType> parameters0) {
// print the request data
String prodId = parameters.getProdId();
ReqHeader reqHeader = parameters.getHeader();
String clientId = reqHeader.getClientId();
Date timestamp = reqHeader.getTimeStamp().toGregorianCalendar()
.getTime();

System.out.println("request data: prodId=" + prodId + ",clientId="
+ clientId + ",timestamp=" + timestamp);

// return data
CreateRespParamsType resp = new CreateRespParamsType();
RespHeader respHeader = new RespHeader();
respHeader.setReturnCode("9999");
respHeader.setReturnMessage("OK");
resp.setProdId("PID01");
resp.setHeader(respHeader);
resp.setProdStatus(ProdStatus.PASS);

parameters0.value=resp;
}

@Override
public void get(GetReqParamsType parameters,
Holder<GetRespParamsType> parameters0) {
// TODO Auto-generated method stub

}

}

7) add sun-jaxws.xml to folder WEB-INF\

refer to http://docs.oracle.com/cd/E17802_01/webservices/webservices/docs/2.0/jaxws/jaxws-war.html

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

<endpoints xmlns='http://java.sun.com/xml/ns/jax-ws/ri/runtime' version='2.0'>

    <endpoint 
        name='demows'
        implementation='com.abc.ws.demows.skeleton.DemoServiceImpl'
        url-pattern='/demoservice'/>
</endpoints>

8) add listener and servlet to 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="demows" version="2.5">

<display-name>demows</display-name>
<listener>
<listener-class>com.sun.xml.ws.transport.http.servlet.WSServletContextListener</listener-class>
</listener>
<servlet>
<servlet-name>demows</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>demows</servlet-name>
<url-pattern>/demoservice</url-pattern>
</servlet-mapping>
</web-app>

9) after deploy to tomcat, browser url: http://localhost:8080/demows/demoservice








10) stub code to call the web service

package com.abc.ws.demows.stub;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.GregorianCalendar;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.ws.Holder;
import javax.xml.ws.Service;

import com.abc.ws.common.ReqHeader;
import com.abc.ws.demows.CreateReqParamsType;
import com.abc.ws.demows.CreateRespParamsType;
import com.abc.ws.demows.DemoService;
import com.abc.ws.demows.DemoService_Service;
import com.abc.ws.demows.skeleton.DemoServiceImpl;

public class ClientTest {

public static void main(String[] args)
throws DatatypeConfigurationException, MalformedURLException {

URL wsdlUrl = new URL("http://localhost:8080/wsdemo/demoservice");

/* if not sure the value of namespaceURL and localPart to init QName, take a look the info  on http://localhost:8080/wsdemo/demoservice , for this case,
Service Name:{http://skeleton.demows.ws.abc.com/}DemoServiceImplService
*/
QName serviceName = new QName("http://skeleton.demows.ws.abc.com/",
"DemoServiceImplService");
Service service = Service.create(wsdlUrl, serviceName);

DemoService port = service.getPort(DemoService.class);
CreateReqParamsType reqPara = new CreateReqParamsType();
ReqHeader header = new ReqHeader();
header.setClientId("CID0001");
header.setTimeStamp(getXMLGregorianCalendarNow());
reqPara.setHeader(header);
reqPara.setProdId("PID02");

CreateRespParamsType respPara = new CreateRespParamsType();
Holder<CreateRespParamsType> resp = new Holder<CreateRespParamsType>();
resp.value = respPara;

port.create(reqPara, resp);

System.out.println("response data:" + resp.value.getProdId() + ","
+ resp.value.getHeader().getReturnCode() + ","
+ resp.value.getHeader().getReturnMessage() + ","
+ resp.value.getProdStatus().value());

}

public static XMLGregorianCalendar getXMLGregorianCalendarNow()
throws DatatypeConfigurationException {
GregorianCalendar gregorianCalendar = new GregorianCalendar();
DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
XMLGregorianCalendar now = datatypeFactory
.newXMLGregorianCalendar(gregorianCalendar);
return now;
}
}

test result:

message printed by skeleton:
request data: prodId=PID02,clientId=CID0001,timestamp=Mon Feb 04 12:29:17 SGT 2013

message printed by stub:
response data:PID01,9999,OK,Pass