Tuesday, June 5, 2007

JAX-WS and how to send SOAP Header

When i do my project, i met a problems. How can i add soap header to soap messages. The webservice is developed with JAX-WS library.

I really don't know why jax-ws does not implements such a method to add soap header from the implementation of web-service. We have to use another way to do that: Soap Handler. A Soap Handler is called before the implementation of webservice called. So we used them to send soap header.

for example:
we have a webservice with one function: sayHello(). They function required that its response and request come along with a soap header. It should contains the value of session.

[soap:header]
[session] 12424512[/session]
[/soap:header]
[soap:body]
[sayhelloresponse] .... [/sayhelloresponse]
[/soap:body]

How we add soap:header??

Step to do:
in the class implement the function sayHello()

@Resource
WebServiceContext wsContext

void sayHello(){
MessageContext message = wsContext.getMessage();
message.put("session", 245235); //this will put the value to the context of the //webservice
}

In the soap handler, we get the value we store in the context of webservice. and put to soap handler. Only in soap handler, we have a chance to create soap header. So that why we have to do in this way.

public class SOAPLoggingHandler implements
SOAPHandler {
...

public boolean handleMessage(SOAPMessageContext smc) {
Boolean outboundProperty = (Boolean)
smc.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

if (outboundProperty) {
MessageContext context = smc.getMessageContext();;
String session = context.get("Session");
SOAPMessage soapMessage = smc.getMessage();
//Add header to soapmessage. Using function of soap message like
//GetElementHeader, GetHeader ... I am not remember exactly. I will update this soon.
}
return true;
}

public boolean handleFault(SOAPMessageContext smc) {
logToSystemOut(smc);
return true;
}

public void close(MessageContext messageContext) {
}
}

Friday, June 1, 2007

JBoss and Log4j

JBoss and Log4j come together will make the stupid things. JBoss make all logs of user application come to its log file and prohibit the user using trace log. It is terrible. In my project, i have to define my own log file which separated with the system log. But this task take me 2 days. What a terrible. Now i find 2 solution.
First one: Remove all the trace log and define new appender and category in log4j.xml in folder conf of jboss. It will make the log of application come to new place.
Seconde one:
Using RepositorySelector with ServletListener or Listener to use your own log. As my research in internet, i found that this is the clean solution of separate logger with jboss logger.
All you need for the second solution is:

  1. Log4j.jar in WEB-INF.
  2. Your own lof4j.xml file( you can mimic the log4j.xml of jboss)
  3. 2 file followings.
  4. Configure web.xml to ensure servlet or servlet listener to run first to make log initialize or you will get error.

/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/

package org.jboss.repositoryselectorexample;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RepositorySelector;
import org.apache.log4j.spi.RootCategory;
import org.apache.log4j.xml.DOMConfigurator;
import org.w3c.dom.Document;

/**
* This RepositorySelector is for use with web applications. It assumes that
* your log4j.xml file is in the WEB-INF directory.
*
* @author Stan Silvert
*/
public class MyRepositorySelector implements RepositorySelector
{
private static boolean initialized = false;
private static Object guard = LogManager.getRootLogger();

private static Map repositories = new HashMap();
private static LoggerRepository defaultRepository;

/**
* Register your web-app with this repository selector.
*/
public static synchronized void init(ServletConfig config)
throws ServletException {
if( !initialized ) // set the global RepositorySelector
{
defaultRepository = LogManager.getLoggerRepository();
RepositorySelector theSelector = new MyRepositorySelector();
LogManager.setRepositorySelector(theSelector, guard);
initialized = true;
}

Hierarchy hierarchy = new Hierarchy(new RootCategory(Level.DEBUG));
loadLog4JConfig(config, hierarchy);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
repositories.put(loader, hierarchy);
}

public static synchronized void removeFromRepository() {
repositories.remove(Thread.currentThread().getContextClassLoader());
}

// load log4j.xml from WEB-INF
private static void loadLog4JConfig(ServletConfig config,
Hierarchy hierarchy)
throws ServletException {
try {
String log4jFile = "/WEB-INF/log4j.xml";
InputStream log4JConfig =
config.getServletContext().getResourceAsStream(log4jFile);
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder()
.parse(log4JConfig);
DOMConfigurator conf = new DOMConfigurator();
conf.doConfigure(doc.getDocumentElement(), hierarchy);
} catch (Exception e) {
throw new ServletException(e);
}
}

private MyRepositorySelector() {
}

public LoggerRepository getLoggerRepository() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
LoggerRepository repository = (LoggerRepository)repositories.get(loader);

if (repository == null) {
return defaultRepository;
} else {
return repository;
}
}
}

/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/

package org.jboss.repositoryselectorexample;

import java.io.*;
import java.net.*;

import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
* This servlet demonstrates initialization of a RepositorySelector so that
* logging can be done on a per-app basis.
*
* @author Stan Silvert
*/
public class Log4JServlet extends HttpServlet {

private static Logger LOG;

private String servletName;
private MyObject myObj; // can't initialize until init() method called

/** Initializes the servlet.
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.servletName = config.getServletName();


MyRepositorySelector.init(config);

// note that we can't call Logger.getLogger() until
// MyRepositorySelector.init() is called. For all other classes in the
// webapp, you can call Logger.getLogger() at any time.
this.LOG = Logger.getLogger(Log4JServlet.class);

// the same goes for the Logger.getLogger() method in MyObject
this.myObj = new MyObject(this.servletName);
}

/** Destroys the servlet.
*/
public void destroy() {
MyRepositorySelector.removeFromRepository();
}

/**
* Processes requests for both HTTP GET and POST
* methods.
* @param request servlet request
* @param response servlet response
*/
protected void processRequest(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {

response.setContentType("text/*");
PrintWriter out = response.getWriter();

LOG.error("TEST FROM THE SERVLET: " + this.servletName);
myObj.foo();
out.println("");
out.close();
}

/** Handles the HTTP GET method.
* @param request servlet request
* @param response servlet response
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/** Handles the HTTP POST method.
* @param request servlet request
* @param response servlet response
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}

/** Returns a short description of the servlet.
*/
public String getServletInfo() {
return "Log4JServlet";
}

}


/***************************************
* *
* JBoss: The OpenSource J2EE WebOS *
* *
* Distributable under LGPL license. *
* See terms of license at gnu.org. *
* *
***************************************/

package org.jboss.repositoryselectorexample;

import org.apache.log4j.Logger;

/**
* This just shows another object besides the servlet within the same
* web app.
*
* @author Stan Silvert
*/
public class MyObject {

// note that this can not be static because MyObject is referenced in
// the servlet before the RepositorySelector is initialized. For other
// classes, it would be OK to make this static.
private final Logger LOG = Logger.getLogger(MyObject.class);

private String servletName;

public MyObject(String servletName) {
this.servletName = servletName;
}
public void foo() {
LOG.info("Message from MyObject: " + this.servletName);
}
}

JMock and how to use.

Today i use JMock for my projects. I should say that it is a good tool for testing.
But finding how to use it makes me crazy. But now i have just resolved all my problems.
It related to the jax-ws mock object. JAX-WS uses the abstract instead of the interface so
it makes using jmock is difficult task. But thanks for new lib JMock 2.x, it support abstract class also concrete class. Following is sample of using jmock for jax-ws soap handler for the unit test purpose.

Step to use:
Using the latest lib JMock 2.x lib

Import org.jmock.Expectations;
import org.jmock.integration.junit3.MockObjectTestCase;
import org.jmock.lib.legacy.ClassImposteriser;

protected final void setUp() throws Exception {
super.setUp();
setImposteriser(ClassImposteriser.INSTANCE);
soapHandlerImpl = new SoapHandlerImpl();
}

public final void testHandleMessage(){
//create mock object
final SOAPMessageContext smc = mock(SOAPMessageContext.class);
final SOAPHeader mockSoapHeader = mock(SOAPHeader.class);
final SOAPMessage mockSoapMessage1 = mock(SOAPMessage.class);
final SOAPMessage mockSoapMessage2 = mock(SOAPMessage.class);
final SOAPHeaderElement mockSoapHeaderElement =
mock(SOAPHeaderElement.class);
final SOAPHeaderElement soapSessionIDHashTag =
mock(SOAPHeaderElement.class);
final SOAPHeaderElement soapFromTag = mock(SOAPHeaderElement.class);
final SOAPHeaderElement soapFromTo = mock(SOAPHeaderElement.class);

//Make the expectation. Please remember that the expectation have to be
//correct or the exception occur. The exception message is not useful to resolve
checking(new Expectations() {
{
one(smc).get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
will(returnValue(true));
}
}); //simulate function getmessage for soapmessage.

//call function to test with mock parameter.
soapHandlerImpl.handleMessage(smc) ;
}
Some note:
To make the test run with out exception, we have to define how many mock object you need in the function. You should also count the number of function of mock object which will be called and the data for each calls. All of them have to be explicit define or you will got an exception in unit test.
I wrote this to remind me the problems when i do my project in company. I met a lot of prolems with that.

Google