/*
* Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The
* University of Hong Kong (HKU). All Rights Reserved.
*
* This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1]
*
* [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
*/
package hk.hku.cecid.corvus.ws;
import java.util.List;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.Authenticator;
import java.net.MalformedURLException;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPConnectionFactory;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.MessageFactory;
import hk.hku.cecid.corvus.util.FileLogger;
import hk.hku.cecid.corvus.util.SOAPUtilities;
import hk.hku.cecid.corvus.ws.data.Data;
import hk.hku.cecid.piazza.commons.module.Component;
import hk.hku.cecid.piazza.commons.util.UtilitiesException;
import hk.hku.cecid.piazza.commons.util.PropertyTree;
/**
* The <code>SOAPSender</code> is a abstract class for the <em>SOAP</em>-Based
* protocol client. It reduces the complexity and version incompatible induced
* from the apache AXIS by using only Built-in java SOAP XML Package.<br/><br/>
*
* The target developer is for those whose are not familiar with the complex
* AXIS framework.<br/><br/>
*
* It is implemented using the event-driven model so that developer
* are only required to implement several event method.<br/><br/>
*
* The package is under development and will be standardized in the future
* release.<br/><br/>
*
* @author Twinsen Tsang
* @version 1.0.3
* @since 0.9.0
*/
public abstract class SOAPSender extends Component implements Runnable {
/**
* The namespace prefix
*/
protected static final String NS_PREFIX = "tns";
/**
* The logger used for log message and exception
*
* @see hk.hku.cecid.corvus.util.FileLogger
*/
protected FileLogger log;
/**
* The data properties for this sender.
*/
protected Data properties;
/**
* The SOAP request
*/
protected SOAPMessage request = null;
/**
* The SOAP response
*/
protected SOAPMessage response = null;
/**
* The url of service end point.
*/
protected URL serviceEndPoint = null;
/**
* The flag indicate whether requires XML declaration at the top.
*/
private boolean isRequireXMLDecl = false;
/**
* The dirty flag for the SOAP request.
*/
private boolean isRequestDirty = true;
/**
* Number of times to sent to Hermes2.
*/
private int loopTimes = 1;
/**
* The current looping times.
*/
private int curTimes = 0;
/**
* THe custom user object for the callback.
*/
private Object userObj = null;
/**
* Inner class for web-services authentication.
*/
private class SOAPAuthenticator extends Authenticator {
/**
* The password authentication pair.
*/
private PasswordAuthentication pwAuth;
/**
* Constructor.
*
* @param username
* The username for authentication.
* @param password
* The password for authentication.
*/
protected SOAPAuthenticator(String username, String password){
pwAuth = new PasswordAuthentication(username, password.toCharArray());
}
/**
* Get the authentication pair.
*/
protected PasswordAuthentication getPasswordAuthentication() {
return pwAuth;
}
}
/**
* SPA Constructor.<br<br>
*
* It is used when the SOAP Sender is a component in the spa.
*/
public SOAPSender(){}
/**
* Constructor
*
* @param l The logger used for log message and exception.
*/
public SOAPSender(FileLogger l, Data d){
this.log = l;
this.properties = d;
try{
this.request = MessageFactory.newInstance().createMessage();
}catch(SOAPException se){
this.onError(se);
}
}
/**
* Constructor
*
* @param l The logger used for log message and exception.
* @param endpoint The url of service end point.
*/
public SOAPSender(FileLogger l, Data d, String endpoint){
this(l,d);
try{
this.serviceEndPoint = new URL(endpoint);
}catch(MalformedURLException mue){
this.onError(mue);
}
}
/**
* Constructor.
*
* @param l The logger used for log message and exception.
* @param endpoint The url of service end point.
*/
public SOAPSender(FileLogger l, Data d, URL endpoint){
this(l,d);
this.serviceEndPoint = endpoint;
}
/**
* Implements this method if you want to send messages without
* much different between other message to sent.
*/
public void initializeMessage() throws Exception{
// Implement by sub-class.
}
/**
* [@EVENT] This method is invoked when the sender begins to execute the
* run method.<br/>
*
*/
public void onStart(){
this.curTimes = 0;
}
/**
* [@EVENT] This method is invoked when each loop iteration
* start.<br/>
*
* @throws Exception Any type of exception will be processed at onError(throwable t).
*/
public void onEachLoopStart() throws Exception{
// Implement by-sub-class
}
/**
* [@EVENT] This method is invoked when the sender is required to
* sent a SOAP Request for configuration.<br/><br/>
*
* The default return value is the request in the sender.<br/>
* <br/>
* <strong>
* If developer want to send a custom SOAP request other than the sender
* SOAP request, override the function and return your
* customizing SOAP Request.
* </strong><br/>
* <br/>
* For example, if you want to send a SOAP request always with
* soap element "test". Then you should override this function
* called.
* <br/>
* <PRE>
* public SOAPMessage onCreateRequest() throws Exception{
* SOAPMessage request = MessageFactory.newInstance().createMessage();
* ..
* ..
* add the element "test".
* return request;
* }
* </PRE>
* <br/>
* @return javax.xml.SOAPMessage
*
* @throws Exception Any type of exception will be processed at onError(throwable t).
*/
public SOAPMessage onCreateRequest() throws Exception{
// Reinitialize the message is the request is dirty.
if (this.isRequestDirty()){
this.resetSOAPRequest();
this.initializeMessage();
}
return this.request;
}
/**
* [@EVENT] This method is invoked just before sending the request to
* Web service endpoints.
*
* @param conn The SOAP Connection used for sending SOAP request.
* @param request The request created by {@link #onCreateRequest()}.
*
* @throws Exception Any type of exception will be processed at onError(throwable t).
*/
public void onBeforeRequest(SOAPConnection conn, SOAPMessage request) throws Exception{
// Implement by sub-class
}
/**
* [@EVENT] This method is invoked when received the reply SOAP
* response from the server
*
* Developer can use {@link #getSOAPResponse()} to get
* the SOAP response for self-handling.
*
* Otherwise, developer can use {@link #getResponseElementText(String, String, int)}
* to get the response element text from the response object.
*
* @throws Exception Any type of exception will be processed at onError(throwable t).
*/
public void onResponse() throws Exception{
// Implement by sub-class
}
/**
* [@EVENT] This method is invoked when the sending execution is ended.
*/
public void onEnd(){
// Implement by sub-class
}
/**
* [@EVENT] This method is invoked when there is any exception thrown
* during web service call.
*/
public void onError(Throwable t){
t.printStackTrace();
if (this.log != null){
this.log.logStackTrace(t);
}
}
/**
* Set how many times should the sender to be send.
*
* @param loopTimes the new loopTimes.
*/
public void setLoopTimes(int loopTimes){
if (loopTimes > 0){
this.loopTimes = loopTimes;
}
}
/**
* Set a user object for callback.
*
* @param obj The user object.
*/
public void setUserObject(Object obj){
this.userObj = obj;
}
/**
* Set the service endpoint.
*
* @param endpoint The URL of the web service endpoint.
*/
public void setServiceEndPoint(URL endpoint){
if (endpoint != null){
this.serviceEndPoint = endpoint;
}
}
/**
* Set the service endpoint.
*
* @param endpoint The String of the web service endpoint.
*/
public void setServiceEndPoint(String endpoint){
try{
this.serviceEndPoint = new URL(endpoint);
}
catch(MalformedURLException mue){
if (this.log != null){
this.log.logStackTrace(mue);
}
}
}
/**
* Set if the request is dirty. We considered "dirty"
* as the request has been modified by someone during
* last sending.
*/
public void setRequestDirty(boolean dirty){
this.isRequestDirty = dirty;
}
/**
* Set if the request requires XML declaration at the top
* of the request.<br/>
* <br/>
* This is equivalent to: <br/>
* <pre>
* SOAPRequest.setProperty(WRITE_XML_DECLARATION, "true");
* </pre>
*
* @param require
* true if requires XML declaration.
*
* @see javax.xml.soap.SOAPMessage#setProperty(String, Object)
* @see javax.xml.soap.SOAPMessage#WRITE_XML_DECLARATION
*/
public void setRequireXMLDeclaraction(boolean require){
this.isRequireXMLDecl = require;
this.isRequestDirty = true;
}
/**
* Set to use the basic authentication when calling
* the web service.
*
* @param username
* The username for basic authentication.
* @param password
* The password for basic authentication.
*/
public void setBasicAuthentication(final String username, final String password){
Authenticator.setDefault(new SOAPAuthenticator(username, password));
}
/**
* Get how many times should the sender to be send.
*/
public int getLoopTimes(){
return this.loopTimes;
}
/**
* Get what is the current loop times for looping
*/
public int getCurrentLoopTimes(){
return this.curTimes;
}
/**
* Get a user object.
*/
public Object getUserObject(){
return this.userObj;
}
/**
* Get the service end-point.
*
* @return the service endpoint URL.
*/
public URL getServiceEndPoint(){
return this.serviceEndPoint;
}
/**
* return return true if the request requires the XML declaration to sent.
*/
public boolean isRequireXMLDeclaraction(){
return this.isRequireXMLDecl;
}
/**
* @return return true if the request is dirty.
*/
public boolean isRequestDirty(){
return this.isRequestDirty;
}
/**
* Get the SOAP request.
*
* @return The SOAP Request Body.
*/
protected SOAPMessage getSOAPRequest(){
return this.request;
}
/**
* Reset the request to empty SOAP Body.<br>
*
* It is commonly used when for each loop times the request
*
* @throws SOAPException
*/
protected void resetSOAPRequest() throws SOAPException{
this.request = MessageFactory.newInstance().createMessage();
}
/**
* Get the SOAP response.<br>
*
* The method should only be called inside {@link #onResponse()}.<br>
*
* @return The SOAP Response Body.
*/
protected SOAPMessage getSOAPResponse(){
return this.response;
}
/**
* Reset the request to empty SOAP Body.<br>
*
* It is commonly used when for each loop times the response.
*/
protected void resetSOAPResponse() throws SOAPException{
this.response = MessageFactory.newInstance().createMessage();
}
/**
* Add SOAP element to body with name and value.
*
* @param tagName The tag name of element to be retrieved.
* @param tagValue The value of the element to be added.
* @param nsPrefix The namespace Prefix
* @param nsURI The namespace URI.
* @return true if the creation and addition is successfully.
* @throws SOAPException
*/
public boolean addRequestElementText(String tagName
,String tagValue
,String nsPrefix
,String nsURI) throws SOAPException{
if (this.request == null)
return false;
SOAPBody soapBody = request.getSOAPPart().getEnvelope().getBody();
if (soapBody == null)
return false;
// Create new element.
SOAPElement newElement = SOAPUtilities.createElement(tagName, tagValue, nsPrefix, nsURI);
soapBody.addChildElement(newElement);
return true;
}
/**
* Add SOAP element to specify parent element
*
* @param parentTagName The tag name of parent element.
* @param parentNsURI The namespace URI of parent element.
* @param tagName The tag name of element to be retrieved.
* @param tagValue The value of the element to be added.
* @param nsPrefix The namespace Prefix
* @param nsURI The namespace URI.
* @return true if the creation and linking is successfully.
* @throws SOAPException
*/
public boolean addRequestElementText(String parentTagName
,String parentNsURI
,String tagName
,String tagValue
,String nsPrefix
,String nsURI) throws SOAPException{
return
this.addRequestElementText(tagName, tagValue, nsPrefix, nsURI) &&
SOAPUtilities.linkElements(this.request, parentTagName, parentNsURI, tagName, nsURI);
}
/**
* This method should only be called inside {@link #onCreateRequest()}.
* because the request object will be deleted upon each ws call.
*
* @param tagname The tag name of element to be retrieved.
* @param nsURI The namespace URI.
* @param whichOne The nth child element to be returned.
*/
public String getRequestElementText(String tagname
,String nsURI
,int whichOne) throws SOAPException{
SOAPElement elem = SOAPUtilities.getElement(this.request, tagname, nsURI, whichOne);
if (elem != null)
return elem.getValue();
return "";
}
/**
* This method should only be called inside {@link #onResponse()}.
* because the response object will be deleted upon each ws call.
*
* @param tagname The tag name of element to be retrieved.
* @param nsURI The namespace URI.
* @param whichOne The nth child element to be returned.
*
* @return The element text in the tagname specified.
*/
public String getResponseElementText(String tagname
,String nsURI
,int whichOne) throws SOAPException{
SOAPElement elem = SOAPUtilities.getElement(this.response, tagname, nsURI, whichOne);
if (elem != null)
return elem.getValue();
return "";
}
/**
* This methods count number of specified <code>tagname</code>
* in the response. It should only be called inside {@link #onResponse()}.
*
* @param tagname The tag name of element to be retrieved.
* @param nsURI The namespace URI.
* @return The element text in the tagname specified.
* @throws SOAPException
*/
public int countResponseElementText(String tagname
,String nsURI) throws SOAPException{
return SOAPUtilities.countElement(this.response, tagname, nsURI);
}
/**
* Transform the response into a property tree.
* It should only be called inside {@link #onResponse()}.
*
* @return An XML Property tree having the same tag content in the response.
*/
public PropertyTree transformResponseContent() throws UtilitiesException, SOAPException {
SOAPBody soapBody = response.getSOAPBody();
if (soapBody == null) return null;
try{
return new PropertyTree(soapBody);
}catch(Exception e){
throw new UtilitiesException("Unable to transform soapBody due to", e);
}
}
/**
* This method should only be called inside {@link #onResponse()}.
* because the response object will be deleted upon each ws call.<br/><br/>
*
* This method get the element by it's <code>tagname</code> and return
* a list of text value inside.
*
* @param tagname
* The name of the XML tag to be extraceted.
* @param nsURI
* The namespace URI.
* @return The elements' text in the tagname specified.
* @throws SOAPException
*/
public String [] getResponseElementAsList(String tagname
,String nsURI) throws SOAPException{
List list = SOAPUtilities.getElementList(this.response, tagname, nsURI);
String [] props = new String[list.size()];
for (int i = 0; i < list.size(); i++){
if (list.get(i) != null)
props[i] = ((SOAPElement)list.get(i)).getValue();
}
return props;
}
/**
* The thread execution method.
*/
public void run()
{
// Variable declaration.
SOAPMessage request = null;
SOAPConnectionFactory factory = null;
// Signals a kick-off event.
this.onStart();
try{
for(int i = 0; i < this.getLoopTimes(); i++){
this.curTimes = i;
// Signals a loop start event.
this.onEachLoopStart();
// Asks child class for creating the request.
request = this.onCreateRequest();
factory = SOAPConnectionFactory.newInstance();
SOAPConnection soapConn = factory.createConnection();
this.onBeforeRequest(soapConn, request);
// Save the request if the developers modify the request
// at somewhere.
if (this.isRequestDirty()){
if (this.isRequireXMLDecl)
this.request.setProperty(SOAPMessage.WRITE_XML_DECLARATION , "true");
this.request.saveChanges();
this.setRequestDirty(false);
}
this.response = soapConn.call(request, this.serviceEndPoint);
// Check whether has SOAP fault from server side.
SOAPBody sb = this.response.getSOAPBody();
if (sb.hasFault()){
throw new SOAPException(
sb.getFault().getFaultCode()
+ " "
+ sb.getFault().getFaultString());
}
this.onResponse();
}
this.onEnd();
// Reset soap request and response.
this.resetSOAPRequest();
this.resetSOAPResponse();
}
catch(Exception e){
this.onError(e);
}
}
}