package vnet.sms.gateway.nettysupport.window.incoming; import static org.apache.commons.lang.Validate.isTrue; import static org.apache.commons.lang.Validate.notNull; import java.io.Serializable; import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import vnet.sms.common.messages.GsmPdu; import vnet.sms.common.wme.WindowedMessageEvent; /** * @author obergner * */ public class IncomingWindowStore<ID extends Serializable> { private final int maximumCapacity; private final long waitTimeMillis; private final ConcurrentMap<ID, GsmPdu> messageReferenceToMessage; private final Semaphore availableWindows; private volatile boolean shutDown = false; // ------------------------------------------------------------------------ // Constructors // ------------------------------------------------------------------------ public IncomingWindowStore(final int maximumCapacity, final long waitTimeMillis) { this.maximumCapacity = maximumCapacity; this.availableWindows = new Semaphore(maximumCapacity); this.waitTimeMillis = waitTimeMillis; this.messageReferenceToMessage = new ConcurrentHashMap<ID, GsmPdu>( maximumCapacity); } // ------------------------------------------------------------------------ // JMX API // ------------------------------------------------------------------------ /** * @see vnet.sms.gateway.nettysupport.window.incoming.IncomingWindowStoreMBean#getMaximumCapacity() */ public final int getMaximumCapacity() { return this.maximumCapacity; } public long getWaitTimeMillis() { return this.waitTimeMillis; } /** * @see vnet.sms.gateway.nettysupport.window.incoming.IncomingWindowStoreMBean#getCurrentMessageCount() */ public final int getCurrentMessageCount() { return this.messageReferenceToMessage.size(); } // ------------------------------------------------------------------------ // Store new message // ------------------------------------------------------------------------ /** * @param channelEvent * @return * @throws IllegalArgumentException * @throws IllegalStateException * @throws InterruptedException */ public boolean tryAcquireWindow( final WindowedMessageEvent<ID, ? extends GsmPdu> messageEvent) throws IllegalArgumentException, IllegalStateException, InterruptedException { notNull(messageEvent, "Cannot store a null message"); isTrue(messageEvent.getMessage() instanceof GsmPdu, "Can only process MessageEvents containing a message of type " + GsmPdu.class.getName() + ". Got: " + messageEvent.getMessage()); ensureNotShutDown(); if (!this.availableWindows.tryAcquire(this.waitTimeMillis, TimeUnit.MILLISECONDS)) { return false; } return storeMessageHavingPermit(messageEvent); } private void ensureNotShutDown() throws IllegalStateException { if (this.shutDown) { throw new IllegalStateException( "This IncomingWindowStore has already been shut down"); } } private boolean storeMessageHavingPermit( final WindowedMessageEvent<ID, ? extends GsmPdu> messageEvent) throws IllegalArgumentException { final GsmPdu storedMessageHavingSameId; if ((storedMessageHavingSameId = this.messageReferenceToMessage .putIfAbsent(messageEvent.getMessageReference(), messageEvent.getMessage())) != null) { throw new IllegalArgumentException("Another message [" + storedMessageHavingSameId + "] having the same ID [" + messageEvent.getMessageReference() + "] has already been stored"); } return true; } // ------------------------------------------------------------------------ // Release message from store // ------------------------------------------------------------------------ /** * @param messageReference * @return * @throws IllegalArgumentException * @throws IllegalStateException */ public GsmPdu releaseWindow(final ID messageReference) throws IllegalArgumentException, IllegalStateException { ensureNotShutDown(); try { final GsmPdu releasedMessage = this.messageReferenceToMessage .remove(messageReference); if (releasedMessage == null) { throw new IllegalArgumentException( "Illegal attempt to release a non-existing message: no message having the supplied messageReference [" + messageReference + "] is stored"); } return releasedMessage; } finally { this.availableWindows.release(); } } // ------------------------------------------------------------------------ // Shutdown this store // ------------------------------------------------------------------------ /** * @return */ public Map<ID, GsmPdu> shutDown() { if (this.shutDown) { return Collections.emptyMap(); } this.shutDown = true; // Volatile write return Collections.unmodifiableMap(this.messageReferenceToMessage); } @Override public String toString() { return "IncomingWindowStore@" + hashCode() + "[maximumCapacity = " + this.maximumCapacity + "|messageReferenceToMessage = " + this.messageReferenceToMessage + "|shutDown = " + this.shutDown + "]"; } }