/*
* 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.http;
import java.io.IOException;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.ConnectException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.w3c.tidy.Tidy;
/**
* The <code>PartnershipOpVerifer</code> is an helper class for verifying whether the partnership
* operation has been executed successfully. Since H2O does not have any build-in SOAP-based
* web service for managing the partnerships under remote behavior, The PartnershipOpVerifer
* acts as a validator for validating the <b>HTML content</b> returning from the H2O
* administration web page.
* <br/><br>
* The main method of this class is {@link #validate(InputStream)} and the arugment
* is the input stream. The input stream SHOULD contains the HTML content after you
* executed add/delete/update partnership in either AS2/EBMS partnership administration page.
*
* @author Twinsen Tsang
* @version 1.0.0 $CHANGE FREQUENTLY$
* @since H2O 28/11
*/
public class PartnershipOpVerifer
{
// Instance logger.
final Logger logger = LoggerFactory.getLogger(this.getClass());
/* The constant HTML content representing the partnership is added successfully */
public static final String OP_ADD_SUCCESS = "Partnership added successfully";
/* The constant HTML content representing the partnership is updated successfully */
public static final String OP_UPDATE_SUCCESS = "Partnership updated successfully";
/* The constant HTML content representing the partnership is deleted successfully */
public static final String OP_DELETE_SUCCESS = "Partnership deleted successfully";
/* The constant HTML content representing the partnership page is ready, no operation executed. */
public static final String OP_NO = "Ready";
/* The constant ERROR message notifying the user to view the log for the root cause why the
* partnership failed to execute.
*/
private static final String CHECK_LOG_RESULTSTR =
"Please check the H2O log to see why the partnership operation fails";
// The factory for creating SAX reader.
private final SAXParserFactory spf = SAXParserFactory.newInstance();
// The external library - Tidy for reformat HTML input to well-formed XHTML.
// private Tidy tidy = new Tidy();
/**
* The <code>PageletContentVerifer</code> is SAX Default handler for extracting the
* result content after executing a partnership operation.
*/
/*
* The format of HTML we want to capture :
*
* <html>
* .
* .
* .
* <td>
* <a name="message"></a>
* <b>Message: </b><font color="blue">Partnership added successfully</font>
* </td>
*/
private static class PageletContentVerifer extends DefaultHandler
{
private String result = "";
private boolean opRanWithNoError = false;
private boolean capturedEnabled = false;
public void startElement(String uri, String localName, String name, Attributes attributes)
throws SAXException
{
if (!name.equals("a") || attributes.getLength() < 1) return;
String lname = attributes.getLocalName(0);
String value = attributes.getValue(0);
if (lname.equals("id") && value.equals("message"))
this.capturedEnabled = true;
}
public void characters(char[] ch, int start, int length)
throws SAXException
{
if (capturedEnabled){
String s = new String(ch, start, length).replaceAll("\\A\\s|\\s\\z", " ");
result += s;
}
}
public void endElement(String uri, String localName, String name)
throws SAXException
{
if (name.equals("td") && this.capturedEnabled){
this.capturedEnabled = false;
}
// Now we process the result, first we split the str by :
String [] token = result.split(":");
if (token.length > 0 && token.length != 2){
// Exception occurred from the HTML or some any strange error
if (token[0].equalsIgnoreCase(OP_NO)){
// Replace with meaning error string.
this.result = CHECK_LOG_RESULTSTR;
}
}
else if (token.length == 2){
token[1] = token[1].trim(); // trim itself
opRanWithNoError = (
token[1].equalsIgnoreCase(OP_ADD_SUCCESS) ||
token[1].equalsIgnoreCase(OP_UPDATE_SUCCESS) ||
token[1].equalsIgnoreCase(OP_DELETE_SUCCESS)
);
}
else {
throw new SAXException("Unknown result content: " + result);
}
}
public boolean getIsVerifiedWithNoError() { return this.opRanWithNoError; }
public String getVerifiedMessage() { return this.result; }
}
/**
* Default constructor.
*/
public PartnershipOpVerifer(){
this.spf.setNamespaceAware(true);
}
/**
* Validate the HTML content received after executed partnership operation. The content
* is passed as a input stream <code>ins</code>.
* <br/><br/>
* This operation is quite expensive because it first transform the whole HTML content
* received to a well-formed XHTML before parsing by the SAX Parser.
*
* @param ins The HTML content to validate the result of partnership operation
* @throws SAXException
* <ol>
* <li>When unable to down-load the HTML DTD from the web. Check your Internet connectivity</li>
* <li>When IO related problems occur</li>
* </ol>
* @throws ParserConfigurationException
* When SAX parser mis-configures.
*/
public void validate(InputStream ins) throws SAXException, ParserConfigurationException
{
if (ins == null)
throw new NullPointerException("Missing 'input stream' for validation");
try{
// TODO: SLOW, It requires two full-scan transformation to find the result of the partnership operation.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
/* Transforms to well-formed XHTML */
Tidy t = new Tidy();
t.setXHTML(true); t.setQuiet(true); t.setShowWarnings(false);
t.parse(ins, baos);
// For debug purpose
// System.out.println(hk.hku.cecid.piazza.commons.io.IOHandler.readString(ins, null));
// System.out.println("Test: " + new String(baos.toByteArray(), "UTF-8"));
/* Pipe to another input stream */
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
// Create a custom SAX handler for parsing the partnership op result from the HTML.
PageletContentVerifer verifer = new PageletContentVerifer();
// Create SAX parser for parsing the HTML coming back after executing partnership operation.
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser parser = spf.newSAXParser();
parser.parse(bais, verifer);
boolean result = verifer.getIsVerifiedWithNoError();
if (!result) throw new SAXException("Fail to execute partnership operation as : " + verifer.getVerifiedMessage());
}
catch(ConnectException cex){
cex.printStackTrace();
throw new SAXException("Seems unable to download correct DTD from the web, behind proxy/firewall?", cex);
}
catch(IOException ioex){
throw new SAXException("IO Error during SAX parsing.", ioex);
}
}
}