/*
* 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.util.Date;
import java.util.Map;
import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.File;
import java.io.FileInputStream;
import java.nio.channels.FileChannel;
import java.nio.channels.Channels;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import hk.hku.cecid.corvus.ws.data.KVPairData;
import hk.hku.cecid.piazza.commons.data.Data;
import hk.hku.cecid.piazza.commons.util.FileLogger;
import hk.hku.cecid.piazza.commons.io.IOHandler;
/**
* The <code>EnvelopSender</code> is abstract base class for sending HTTP remote request
* to H2O for querying the message envelop through the administration page.
*
* @author Twinsen Tsang
* @version 1.0.0
* @since H2O 28/11/2007
*/
public class EnvelopQuerySender extends HttpSender
{
/** The constant field representing the standardized outgoing message box representation */
public static final String MSGBOX_OUT = "OUTBOX";
/** The constant field representing the standardized incoming message box representation */
public static final String MSGBOX_IN = "INBOX";
/** The constant field representing the HTTP request parameter name for Message id. */
protected static final String MSGID_FORM_PARAM = "message_id";
/** The constant field representing the HTTP request parameter name for Message box. */
protected static final String MSGBOX_FORM_PARAM = "message_box";
// Get the base path for storing the response.
private static String BASE_PATH = System.getProperty("java.io.tmpdir") + File.separator;
private static int THRESHOLD = 1048576;
/* The message id to query the message envelop. */
private String messageIdToDownload = null;
/* The message box to query the message envelop. */
private String messageBoxToDownload = null;
/* The message envelop returned stream. */
private InputStream envelopStream;
/**
* Explicit Constructor. Create an instance of <code>EnvelopQuerySender</code>
*
* @param logger The logger for log the sending process.
* @param d The data used for generate Envelop query request. It must be a kind of Admin data.
*/
protected EnvelopQuerySender(FileLogger logger, Data d){
super(logger, d);
}
/**
* Get the message box mapping from standardized representation to proprietary representation.
* <br/><br/>
* It should return a HashMap like this:
* <pre>
* HashMap m = new HashMap();
* m.put(MSGBOX_IN , "Your Inbox representation");
* m.put(MSGBOX_OUT, "Your outbox representation");
*
* @return the message box mapping.
*
* @see EnvelopQuerySender#MSGBOX_IN
* @see EnvelopQuerySender#MSGBOX_OUT
*/
protected Map getMessageBoxMapping(){
return null;
}
/**
* Set the message criteria for down-load the message envelop (and payload).
*
* @param messageId The message id to down-load the message envelop.
* @param messageBox The message box to down-load the message envelop. either INBOX or OUTBOX.
*
* @throws NullPointerException
* When {@link #getMessageIdToDownload()} return null.<br/>
* When {@link #getMessageBoxToDownload()} return empty or null.
* @throws IllegalArgumentException
* When {@link #getMessageBoxToDownload()} return string not equal to 'INBOX' and 'OUTBOX'
*/
public final void setMessageCriteriaToDownload(String messageId, String messageBox)
{
this.checkArguments(messageId, messageBox);
this.messageBoxToDownload = messageBox;
this.messageIdToDownload = messageId;
}
/**
* @return The message id to down-load the message envelop.
*/
public final String getMessageIdToDownload(){
return this.messageIdToDownload;
}
/**
* @return The message box to donw-load the message envelop. either INBOX or OUTBOX.
*/
public final String getMessageBoxToDownload(){
return this.messageBoxToDownload;
}
/**
* This method should be called after executed {@link #run()} successfully.
*
* @return The message envelop stream.
*/
public final InputStream getEnvelopStream() throws IOException {
if (this.envelopStream == null)
throw new IOException("There is no envelop stream available, exeucted run() properly ?.");
return this.envelopStream;
}
/**
* [@EVENT] The method <code>onStart</code> log all new configuration.
*/
protected void onStart()
{
// Get the data.
KVPairData data = (KVPairData) this.properties;
super.onStart();
if (this.log != null){
FileLogger l = this.log;
// Log all information for this sender.
l.log("Envelop Query HTTP Client init at " + new Date().toString());
l.log("");
l.log("Sending Envelop Query HTTP Request with following configuration");
l.log("------------------------------------------------------------------");
if (data != null)
l.log(data.toString());
l.log("------------------------------------------------------------------");
l.log("");
}
}
/**
* [@EVENT] This method is invoked when the sender is required to create a HTTP Request from configuration.
* <br/><br/>
* It generates a form-url-encoded content embedded in the HTTP POST request. It contains
* two parameters, message_id and message_box. The value of these parameters are
* extracted from {@link #getMessageIdToDownload()} and {@link #getMessageBoxToDownload()}
* respectively.
* <br/><br/>
* <b>NOTE</b>: The values of message_box parameter may differ to what you see because it
* may transform {@link #getMessageBoxMapping()}.
*
* @throws NullPointerException
* When {@link #getMessageIdToDownload()} return null.<br/>
* When {@link #getMessageBoxToDownload()} return empty or null.
* @throws IllegalArgumentException
* When {@link #getMessageBoxToDownload()} return string not equal to 'INBOX' and 'OUTBOX'
*/
protected HttpMethod onCreateRequest() throws Exception
{
this.checkArguments(this.messageIdToDownload, this.messageBoxToDownload);
// Create HTTP Form POST method
PostMethod post = new PostMethod(this.getServiceEndPoint().toExternalForm());
// transform the message box value.
String mappedMsgBox;
Map messageBoxMapping = this.getMessageBoxMapping();
if (messageBoxMapping == null){
this.log.warn("No Message Box mapping found, use NO-OP mapping.");
mappedMsgBox = this.messageBoxToDownload;
} else {
mappedMsgBox = (String) messageBoxMapping.get(this.messageBoxToDownload);
}
// Assign the message_id, message_box as the post parameter.
post.setParameter(MSGID_FORM_PARAM , this.messageIdToDownload);
post.setParameter(MSGBOX_FORM_PARAM , mappedMsgBox);
return post;
}
/**
* [@EVENT] This method is invoked when received the reply HTTP response from the server.
* <br/><br/>
* It saves the response body stream and then available to get through by {@link #getEnvelopStream()}
*/
protected void onResponse() throws Exception
{
HttpMethod post = this.getExecutedMethod();
InputStream ins = post.getResponseBodyAsStream();
/*
* We have to pipe the content to either memory or storage because the response stream
* is directly extracted from socket which is going to close upon the connection
* has been closed.
*/
if (ins.available() < THRESHOLD){
byte [] envelop = IOHandler.readBytes(ins);
this.envelopStream = new ByteArrayInputStream(envelop);
} else {
// Create a temporary file at TMP directory.
File envelopTmp = new File(BASE_PATH + this.hashCode());
envelopTmp.deleteOnExit();
// Pipe the content to the TMP file.
FileChannel fChannel = new FileInputStream(envelopTmp).getChannel();
fChannel.transferFrom(Channels.newChannel(ins), 0, ins.available());
fChannel.close();
// Create an buffered stream to the file.
this.envelopStream = new BufferedInputStream(new FileInputStream(envelopTmp));
// InputStream is closed automatically.
}
}
/*
* A Helper method to check whether the arguments passed is valid.
*/
private final void checkArguments(String messageId, String messageBox){
if (messageId == null)
throw new NullPointerException("Missing 'messageId' in the argument.");
if (messageBox == null || messageBox.equals(""))
throw new NullPointerException("Missing 'messageBox' or it should not be empty.");
if (!messageBox.equals(MSGBOX_OUT) && !messageBox.equals(MSGBOX_IN))
throw new IllegalArgumentException("Invalid 'messageBox' arugments. It should either INBOX or OUTBOX.");
}
}