Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Preventing Browser double clicks to java web app

Status
Not open for further replies.

richardbergquist

Programmer
Feb 3, 2002
31
NZ
Hi all,

We have a java web app (running on Tomcat) that performs an user registration process. A pretty standard internet experience right? We have havning trouble with users double clicking on the 'I accept' button and getting errors because the second transaction fails as the user details has already been recorded in the back end.

Now this must be a really common problem. It seems many sites get around it with java script - which is not a real option as it means different browsers may or may not perform the javascript. We have to implement a solution in the backend (i.e Tomcat servlets) Anybody done this before ?

We have set up some classes that perform a form of transaction locking by interting data into the HttpSession which kinda works. It allows the transaction to be performed only once. Trouble is on the second click we need to navigate somewhere but we don't know the success of the first click.

Before I launch off into a whole bunch of session objects wrangling I thought I'd ask if anybody has come across this and solved it in a nice clean way.

My other solution, is of course, just to shoot the users how can't use a browser correctly.

RjB

RjB.
 
add a client side javascript to disable the button just before the form is submitted.

-pete
 
Thanks Pete - unfortantely javascript is a no go solution as site standards prohibit. Even if we did use it the fix would be then browser dependent - so if the browser had javascript disabled then such a fix wouldn't work.

RjB.
 
Trouble is on the second click we need to navigate somewhere but we don't know the success of the first click.

Is this button a <input type&quot;submit&quot; element? If it is it should not perform a duplicate submit once the form is submitted. Perhaps you could clarify the procedures to duplicate the behavior you are seeing.

-pete
 
Thanks all,

Turns out its as I thought - had to do it the hard way.

Basically I implemented 3 classes
1. WebTxnLock : A class that models a lock on a named transaction, containing methods like lock(), unlock()

2. WebTxnLockList : A class that maintains a list of WebTxnLock's that are retrivable by name

3. WebTxnLockManager
This class looks up the HttpSession to see if a WebTxnLockList exists in the session containing a lock for the transaction in question (if not then creating).

When a request comes into a servlet it must detect if a lock for the txn exists in the session and if its still running or completed. If its completed it determines the outcome of the previous txn and navigates accordingly. However if its still running its got to wait until the first txn completes then then navigates accordingly on the outcome.

here are some code snippets to give the general idea.

Implementation of the locking mechanism in the servlet request :
mTxnLockMgr = new WebTxnLockManager(session);
WebTxnLock lock = mTxnLockMgr.acquireLock(mTxnName);
mLogger.info(&quot;Acquired lock. Transaction never started=&quot; + lock.isUnstarted());
lock = mTxnLockMgr.sleepForRelease(lock);
if (lock.isLocked())
{
if (lock.isRunning())
{
mLogger.error(&quot;Waited on locked txn up gave up waiting. Sorry it didn't work out. Fowards to system error&quot;);
} else if (lock.isFinishedOk())
{
mLogger.info(&quot;Successful txn occured in locked txn. &quot;);
} else if (lock.isFinishedFail())
{
mLogger.info(&quot;Failed txn had occured in locked txn. &quot;);
}
return;
}

//Got the unstarted lock as its a txn that never run for this session. Lock it.
lock.lock();
mLogger.info(&quot;Got the lock. &quot;);


//Do the txn (or minmic is work)
try
{
Thread.sleep(1000*5);
} catch (InterruptedException exInterupt) { }

mLogger.info(&quot;Unlocking and notifying the lock. &quot;);
lock.unlock(true);


General outline of the lock manager :

public class WebTxnLockManager implements ModelConstants
{
//Constants
public static final int SLEEP_MILLIS = 500;
public static final int GIVEUP_MILLIS = 1000*60;

//Static Members
private static Logger mLogger = Logger.getLogger(WebTxnLockManager.class);

//Members
private HttpSession mSession;

/**
* Constructor
* @param HttpSession
*/
public WebTxnLockManager(HttpSession pSession)
{
mSession = pSession;
}


/**
* Gets a WebTxnLock object for the transaction name
* @param String
* @return WebTxnLock
*/
public WebTxnLock acquireLock(String pTxnName)
{
info(mSession, &quot;Acquiring lock for txn &quot; + pTxnName);

WebTxnLockList lockList = (WebTxnLockList) mSession.getAttribute(TXN_LOCK_LIST);
if (lockList == null)
{
debug(mSession, &quot;Created new WebTxnLockList&quot;);
lockList = new WebTxnLockList();
mSession.setAttribute(TXN_LOCK_LIST, lockList);
}

return lockList.getWebTxnLock(pTxnName);
} //eom

/**
* Gets a WebTxnLock object for the transaction name
* @param String
* @return WebTxnLock
*/
public WebTxnLock reacquireLock(WebTxnLock pLock)
{
return acquireLock(pLock.getName());
} //eom

/**
* Waits on a WebTxnLock to complete via a poll-sleep method.<br>
* This method provides a quicker response time than the waitForRelease()
* method.
* <p/>
* @param WebTxnLock
* @return WebTxnLock The lock that was released (i.e. finished running)
* or the still running lock if gave up waiting after 60 secs
*/
public WebTxnLock sleepForRelease(WebTxnLock pLock)
{
int sleepCycle = 1;

if (pLock.isLocked() )
{
warn(mSession, &quot;This transaction had a lock that indicated it had already run. Will initiate wait for release.&quot;);
//Wait until its unlocked
while (pLock.isRunning() && (sleepCycle*SLEEP_MILLIS < GIVEUP_MILLIS) )
{
debug(mSession, &quot;Txn still running. Performing sleep cycle &quot; + sleepCycle);
try { Thread.sleep(SLEEP_MILLIS); }
catch (InterruptedException exInterrputed) {}
pLock = reacquireLock(pLock);
info(mSession, &quot;Reacquired the lock after sleep. Still running=&quot; + pLock.isRunning());
sleepCycle++;
}

} else
{
info(mSession, &quot;No wait for lock requried.&quot;);
}
return pLock;
} //eom


} //eoc

RjB.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top