/*
Copyright 1996-2008 Ariba, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
$Id: //ariba/platform/util/core/ariba/util/core/TokenTable.java#5 $
*/
package ariba.util.core;
import ariba.util.log.Log;
import java.util.Random;
import java.util.List;
/**
A table of tokens that map to objects. Objects can be stored in the
TokenTable. A token is returned that can be used to remove the object.
The object is invalidated after the expireTime milliseconds.
@aribaapi private
*/
public class TokenTable
{
/*-----------------------------------------------------------------------
Private Constants
-----------------------------------------------------------------------*/
// number of attempts to generate unique random token
private static final int MaxTries = 10;
// number of millseconds that token is valid
private static final long DefaultExpiredTime = Date.MillisPerMinute * 6;
/*-----------------------------------------------------------------------
Private Fields
-----------------------------------------------------------------------*/
// the list of token objects
private List tokens;
// the expiration time for this token table
private long expireTime;
// a random number generator
private Random random;
// random object used for locking
private Object lock;
/*-----------------------------------------------------------------------
Constructors
-----------------------------------------------------------------------*/
/** Creates a new TokenTable with the default expiration time. */
public TokenTable ()
{
this(DefaultExpiredTime);
}
/**
Creates a new TokenTable. The expiration time is set to
<b>expireTime</b>, which should be expressed in milliseconds.
*/
public TokenTable (long expireTime)
{
this.lock = new Object();
this.tokens = ListUtil.list();
this.random = new Random();
this.expireTime = expireTime;
}
/*-----------------------------------------------------------------------
Public Methods
-----------------------------------------------------------------------*/
/**
This method inserts the given object into the token table and assigns
it a unique random string for lookup. If we are unable to generate a
unique token (which is extremely unlikely), return null. Otherwise,
return the unique string which can be used to retrieve the object.
*/
public String insert (Object object)
{
boolean looking = true;
String token = null;
int i;
for (i = 0; i < MaxTries && looking; i++)
{
// generate a random token
long l = random.nextLong();
if (l < 0) {
l = -l;
}
token = Long.toString(l, 36);
token = token.toUpperCase();
// check that it is unique
looking = false;
synchronized (lock) {
for (int j=0; j < tokens.size(); j++) {
TokenEntry entry = (TokenEntry)tokens.get(j);
if (entry.token.equals(token)) {
looking = true;
break;
}
}
}
}
// something is very wrong if we didn't get a unique token
// in MaxTries tries
if (i > MaxTries) {
Log.serverOps.debug("insert() couldn't add token for %s", object);
return null;
}
// insert the object into our list
TokenEntry tokenEntry = new TokenEntry(token, object);
synchronized (lock) {
tokens.add(tokenEntry);
}
Log.serverOps.debug("insert() added token object (%s, %s)", token, object);
return token;
}
/**
This method looks up the object stored in our table for the given
token string. It also removes any objects in the table that have
expired.
*/
public Object lookup (String token)
{
TokenEntry found = null;
synchronized (lock) {
long now = System.currentTimeMillis();
for (int i=0; i < tokens.size(); i++) {
TokenEntry temp = (TokenEntry)tokens.get(i);
if (now - temp.timestamp > expireTime) {
removeTokenAtIndex(i, now, temp);
i--;
}
else if (temp.token.equalsIgnoreCase(token)) {
found = temp;
Log.serverOps.debug("lookup() found token %s", temp.token);
break;
}
}
}
if (found == null) {
return null;
}
else {
return found.object;
}
}
/**
This method removes and returns the object stored in our table for the
given token string. It also removes any objects that have expired.
*/
public Object remove (String token)
{
TokenEntry removed = null;
synchronized (lock) {
long now = System.currentTimeMillis();
for (int i=0; i < tokens.size(); i++) {
TokenEntry temp = (TokenEntry)tokens.get(i);
if (now - temp.timestamp > expireTime) {
removeTokenAtIndex(i, now, temp);
i--;
}
else if (temp.token.equalsIgnoreCase(token)) {
removed = (TokenEntry)tokens.get(i);
tokens.remove(i);
Log.serverOps.debug("remove() found token %s", temp.token);
break;
}
}
}
if (removed == null) {
return null;
}
return removed.object;
}
private void removeTokenAtIndex (int i, long now, TokenEntry entry)
{
Log.serverOps.debug("removed token '%s' because it expired " +
"(current = %s, timestamp = %s, expireTime = %s)",
entry.token, Long.toString(now),
Long.toString(entry.timestamp),
Long.toString(expireTime));
tokens.remove(i);
}
public String toString ()
{
FastStringBuffer result = new FastStringBuffer();
synchronized (lock) {
for (int j=0; j < tokens.size(); j++) {
TokenEntry temp = (TokenEntry)tokens.get(j);
result.append(temp.toString());
}
}
return result.toString();
}
}
/** An entry in the token table. */
class TokenEntry
{
String token;
long timestamp;
Object object;
TokenEntry (String token, Object object)
{
this.timestamp = System.currentTimeMillis();
this.token = token;
this.object = object;
}
public String toString ()
{
String time = Long.toString(timestamp);
return Fmt.S("(%s,%s,%s)", token, time, object);
}
}