package hk.hku.cecid.edi.sfrm.handler; import hk.hku.cecid.piazza.commons.module.Component; import hk.hku.cecid.edi.sfrm.pkg.SFRMMessage; import hk.hku.cecid.edi.sfrm.pkg.SFRMConstant; import hk.hku.cecid.edi.sfrm.pkg.SFRMMessageClassifier; import hk.hku.cecid.edi.sfrm.util.TimedOutHashTable; /** * The <code>SFRMDoSHandler</code> is a simple barrier to ensure * there is ONLY one-thread working per segment.<br/><br/> * * When an incoming message is received and prepare to process, the * <strong>IMH</strong> invoke {@link #enter(SFRMMessage)} asking * the DoSHandler to insert the working record for this segment. * Then if there is a duplicate message received, the DosHandler * reject it due to the working record has already exist.<br/><br/> * * Thus it guarantees ONE THREAD WORKING per segment semantics. * <br/><br/> * * <strong>CAUTION</strong>: When the thread in the working record * is not alive, the DoSHandler considers the working record is * redundant and <strong>ALLOW</strong> message with same * composite key owning a barrier for that message. * * Creation Date: 28/6/2007 * * @author Twinsen Tsang * @version 1.0.0 * @since Dwarf 10606 */ public class SFRMDoSHandler extends Component { // The backed hash table supporting time out. private TimedOutHashTable ddosTable = new TimedOutHashTable(); /** * Invoke for requesting a barrier for <code>message</code>. * <br/><br/> * For this case, the requested barrier does not expire. * * @param message The incoming SFRM Message. * @return if the message has been entered by other thread before, * it return false. otherwise, the barrier for this * <code>message</code> is created and the owner is * the invocation thread. */ public boolean enter(final SFRMMessage message){ return this.enter(message, -1); } /** * Invoke for requesting a barrier for <code>message</code>. * * @param message The incoming SFRM Message. * @param lifetime How long is the barrier expire in millisecond. * The common use for this is managing timeout/retry for * a message. * * @return if the message has been entered by other thread before, * it return false. otherwise, the barrier for this * <code>message</code> is created and the owner is * the invocation thread. */ public boolean enter(final SFRMMessage message, long lifetime){ if (message == null) return false; String key = this.getResolvedKey(message); // Check whether if there is any thread working on this message. // External Synchronization is required because only the hash // table guarantees only thread safety of single operation like #get. // If multiple operation like get and put, it doesn't perform // atomicity unless adding synchronized block. synchronized(this){ Thread t = (Thread) ddosTable.get(key); // Create working only if the working thread is null or is done already. if (t == null || (t != null && !t.isAlive())){ if (lifetime == -1) ddosTable.put(key, Thread.currentThread()); else ddosTable.put(key, Thread.currentThread(), lifetime); return true; } } return false; } /** * Invoke for removing the barrier for a <code>message</code>. * <br/><br/> * The internal barrier for this <code>message</code> is removed * and therefore invocating {@link #enter(SFRMMessage)} for this * <code>mesasge</code> return true again. * * @param message The incoming SFRM Message. * @return it returns true iff the working record exists and remove * successfully. */ public boolean exit(final SFRMMessage message){ return (ddosTable.remove(this.getResolvedKey(message)) != null); } /** * Get the composite key from the <code>message</code>. * <br/><br/> * What it does is generating one string indentifying the message. * <br/><br/> * For example: * <pre> * Input message id: test@message-id * Input segment type: PAYLOAD * Input segment number: 999 * * Then the resolved key is <em>test@message-id_INBOX_PAYLOAD_999</em> * </pre> * * @param message The incoming SFRM Message. * @return the composite key of thie SFRM Message. */ public String getResolvedKey(final SFRMMessage message) { SFRMMessageClassifier mc = message.getClassifier(); if (mc.isMeta()){ return message.getMessageID(); } else { return new StringBuffer(message.getMessageID()) .append("_") .append(SFRMConstant.MSGBOX_IN) .append("_") .append(message.getSegmentType()) .append("_") .append(message.getSegmentNo()).toString(); } } }