/*
* Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.cm;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jxta.impl.cm.SrdiIndex.Entry;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.util.backport.java.util.NavigableSet;
import net.jxta.impl.util.backport.java.util.TreeMap;
import net.jxta.impl.util.ternary.TernarySearchTreeImpl;
import net.jxta.impl.util.ternary.wild.WildcardTernarySearchTree;
import net.jxta.impl.util.ternary.wild.WildcardTernarySearchTreeImpl;
import net.jxta.logging.Logging;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
public class InMemorySrdiIndexBackend implements SrdiIndexBackend {
private final static transient Logger LOG = Logger.getLogger( InMemorySrdiIndexBackend.class.getName( ) );
private static final String WILDCARD = "*";
private static final String REGEX_WILDCARD = ".*";
// Store of back end objects in use so we can support the static clear functionality
private static Hashtable<PeerGroup, List<SrdiIndexBackend>> backends = new Hashtable<PeerGroup, List<SrdiIndexBackend>>( );
// Dummy object used in HashMaps
private static final Object OBJ = new Object( );
// Value counter - used to index ItemIndex objects
static long counter = 0;
// Wild card ternary tree of values to peerID lists
WildcardTernarySearchTree<List<PeerIDItem>> peeridValueIndex = new WildcardTernarySearchTreeImpl<List<PeerIDItem>>( );
// The GC Index
TreeMap expiryIndex = new TreeMap( );
// Used by removal to complete expire time for gc cleanup
TernarySearchTreeImpl peerRemovalIndex = new TernarySearchTreeImpl( );
// Stopped indicator
private boolean stopped = false;
// Usage name for this index
private final String indexName;
public InMemorySrdiIndexBackend( PeerGroup group, String indexName ) {
this.indexName = indexName;
List<SrdiIndexBackend> idxs = backends.get( group );
if ( null == idxs ) {
idxs = new ArrayList<SrdiIndexBackend>( 1 );
backends.put( group, idxs );
}
idxs.add( this );
peerRemovalIndex.setNumReturnValues( -1 );
if ( Logging.SHOW_INFO && LOG.isLoggable( Level.INFO ) ) {
LOG.info( "[" + ( ( group == null ) ? "none" : group.toString( ) ) + "] : Initialized " + indexName );
}
}
public static void clearSrdi( PeerGroup group ) {
if ( Logging.SHOW_INFO && LOG.isLoggable( Level.INFO ) ) {
LOG.info( "Clearing SRDIs for " + group );
}
List<SrdiIndexBackend> idxs = backends.get( group );
if ( null != idxs ) {
for ( SrdiIndexBackend idx : idxs ) {
try {
idx.clear( );
} catch ( IOException e ) {
if ( Logging.SHOW_SEVERE && LOG.isLoggable( Level.SEVERE ) ) {
LOG.log( Level.SEVERE, "Failed clearing index for group: " + group.getPeerGroupName( ), e );
}
}
}
backends.remove( group );
}
}
private void stoppedCheck( ) throws IllegalStateException {
if ( stopped ) {
throw new IllegalStateException( this.getClass( ).getName( ) + " has been stopped!" );
}
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#clear()
*/
public synchronized void clear( ) throws IOException {
if ( !stopped ) {
if ( Logging.SHOW_WARNING && LOG.isLoggable( Level.WARNING ) ) {
LOG.warning( "Clearing an index that has not been stopped!" );
}
}
this.expiryIndex.clear( );
this.peerRemovalIndex.deleteTree( );
this.peeridValueIndex.deleteTree( );
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#garbageCollect()
*/
public synchronized void garbageCollect( ) throws IOException {
if ( this.stopped ) {
// Index is stopped... nothing to do
return;
}
if ( Logging.SHOW_INFO && LOG.isLoggable( Level.INFO ) ) {
LOG.info( "gc... " );
}
Long now = Long.valueOf( TimeUtils.timeNow( ) );
NavigableSet exps = this.expiryIndex.navigableKeySet( );
// If we have some work to do...
if ( !exps.isEmpty( ) ) {
if ( ((Long)exps.first( )).compareTo( now ) < 0 ) {
Iterator it = exps.iterator( );
Long exp;
while ( it.hasNext( ) ) {
exp = (Long)it.next( );
if ( exp.compareTo( now ) > 0 ) {
// We've reached the end of this gc scan
break;
}
if ( Logging.SHOW_FINE && LOG.isLoggable( Level.FINE ) ) {
LOG.fine( "Expired: " + exp + " is less than:" + now );
}
HashMap items = (HashMap)this.expiryIndex.get( exp );
ArrayList<IndexItem> removalKeys = new ArrayList<IndexItem>( );
if( null != items ){
for ( Object itemObj : items.values( ) ) {
IndexItem item = (IndexItem) itemObj;
List<PeerIDItem> pids = this.peeridValueIndex.find( item.getTreeKey( ) );
if ( !this.peeridValueIndex.delete( item.getTreeKey( ) ) ) {
if ( Logging.SHOW_SEVERE && LOG.isLoggable( Level.SEVERE ) ) {
LOG.severe( "Failed deleting from PeerId Value Index using key: " + item.getTreeKey( ) );
}
}
if ( null != pids && pids.size( ) > 1 ) {
// Other peer IDs sharing the same key
for ( PeerIDItem pid : pids ) {
if ( pid.getPeerid( ).getUniqueValue( ).toString( )
.equals( item.getIpid( ).getPeerid( ).getUniqueValue( ).toString( ) ) ) {
pids.remove( pid );
break;
}
}
this.peeridValueIndex.insert( item.getTreeKey( ), pids );
}
if ( Logging.SHOW_FINE && LOG.isLoggable( Level.FINE ) ) {
LOG.fine( "TST size: " + this.peeridValueIndex.getSize( ) );
}
removalKeys.add( item );
}
items = null;
}
// Must delete via the iterator
it.remove( );
for ( IndexItem item : removalKeys ) {
// Remove from current position in the expire index
removeGcItem( item );
}
removalKeys = null;
}
}
}
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#getRecord(java.lang.String, java.lang.String, java.lang.String)
*/
public synchronized List<Entry> getRecord( String pkey, String skey, String value )
throws IOException {
stoppedCheck( );
ArrayList<Entry> entries = new ArrayList<Entry>( );
String treeKey = pkey + "\u0800" + skey + "\u0801" + value;
List<PeerIDItem> ipids;
if ( null != ( ipids = this.peeridValueIndex.find( treeKey ) ) ) {
for ( PeerIDItem ipid : ipids ) {
if ( ipid.getExpiry( ) > TimeUtils.timeNow( ) ) {
entries.add( ipid.toEntry( ) );
}
}
}
return entries;
}
private void processKeyList( List<String> keys, HashMap<PeerID, Object> results, int threshold ) {
processKeyList( keys, results, threshold, null );
}
private void processKeyList( List<String> keys, HashMap<PeerID, Object> results, int threshold, String regex ) {
int resultCount = 0;
if( null != keys ){
for ( String key : keys ) {
if ( null != regex ) {
if ( !key.matches( regex ) ) {
continue;
}
}
List<PeerIDItem> pids = this.peeridValueIndex.find( key );
if ( null != pids ) {
for ( PeerIDItem pid : pids ) {
if ( !results.containsKey( pid.getPeerid( ) ) && ( pid.getExpiry( ) >= TimeUtils.timeNow( ) ) ) {
results.put( pid.getPeerid( ), OBJ );
resultCount++;
if ( resultCount == threshold ) {
return;
}
}
}
}
}
}
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#query(java.lang.String, java.lang.String, java.lang.String, int)
*/
public synchronized List<PeerID> query( String pkey, String skey, String value, int threshold )
throws IOException {
stoppedCheck( );
HashMap<PeerID, Object> results = new HashMap<PeerID, Object>( );
if ( null == skey ) {
// All peer IDs who have records under the primary key that have not expired
String treeKey = pkey + "\u0800";
List<String> keys = this.peeridValueIndex.matchPrefix( treeKey, -1 );
processKeyList( keys, results, threshold );
} else {
String treeKey = pkey + "\u0800" + skey + "\u0801" + value;
if ( value.contains( WILDCARD ) ) {
// Support for top and tail wild cards, not supported by WildcardTernaryTree
if ( value.startsWith( WILDCARD ) && value.endsWith( WILDCARD ) ) {
// Perform partial match and use string pattern matching for the rest
treeKey = pkey + "\u0800" + skey + "\u0801";
List<String> keys = this.peeridValueIndex.matchPrefix( treeKey, -1 );
processKeyList( keys, results, threshold, value.replace( WILDCARD, REGEX_WILDCARD ) );
} else {
List<String> keys = this.peeridValueIndex.search( treeKey, -1 );
processKeyList( keys, results, threshold );
}
} else {
List<PeerIDItem> pids = this.peeridValueIndex.find( treeKey );
int resultCount = 0;
if ( null != pids ) {
for ( PeerIDItem pid : pids ) {
if ( !results.containsKey( pid.getPeerid( ) ) && ( pid.getExpiry( ) >= TimeUtils.timeNow( ) ) ) {
results.put( pid.getPeerid( ), OBJ );
resultCount++;
if ( resultCount == threshold ) {
break;
}
}
}
}
}
}
return new ArrayList<PeerID>( results.keySet( ) );
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#remove(net.jxta.peer.PeerID)
*/
@SuppressWarnings( "unchecked" )
public synchronized void remove( PeerID pid ) throws IOException {
stoppedCheck( );
HashMap<Long, IndexItem> items = (HashMap<Long, IndexItem>) this.peerRemovalIndex.get( pid.getUniqueValue( ).toString( ) );
if(items == null) // Nothing to do...
return;
Iterator<Long> it = items.keySet( ).iterator( );
IndexItem iitem = null;
ArrayList<IndexItem> removalKeys = new ArrayList<IndexItem>( );
while ( it.hasNext( ) ) {
iitem = items.get( it.next( ) );
removalKeys.add( iitem );
// Expire this entry
iitem.getIpid( ).setExpiry( -1 );
// Re-add at new position in gc index
addGcItem( iitem );
}
for ( IndexItem item : removalKeys ) {
// Remove from current position in the expire index
removeGcItem( item );
}
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#stop()
*/
public synchronized void stop( ) {
this.stopped = true;
}
/* (non-Javadoc)
* @see net.jxta.impl.cm.SrdiIndexBackend#add(java.lang.String, java.lang.String, java.lang.String, net.jxta.peer.PeerID, long)
*/
@SuppressWarnings( "unchecked" )
public synchronized void add( String pkey, String skey, String value, PeerID pid, long expiry ) {
stoppedCheck( );
if ( Logging.SHOW_FINE && LOG.isLoggable( Level.FINE ) ) {
LOG.fine( "[" + indexName + "] Adding " + pkey + "/" + skey + " = \'" + value + "\' for " + pid );
}
String treeKey = pkey + "\u0800" + skey + "\u0801" + value;
List<PeerIDItem> pids;
String pidString = pid.getUniqueValue( ).toString( );
if ( null != ( pids = this.peeridValueIndex.find( treeKey ) ) ) {
// Check for a PeerID entry already added
for ( PeerIDItem item : pids ) {
String peerIDString = item.getPeerid( ).getUniqueValue( ).toString( );
if ( peerIDString.equals( pidString ) ) {
removeGcItem( item, peerIDString );
pids.remove( item );
break;
}
}
} else {
pids = new ArrayList<PeerIDItem>( 1 );
this.peeridValueIndex.insert( treeKey, pids );
}
PeerIDItem ipid = new PeerIDItem( pid, expiry );
pids.add( ipid );
IndexItem idxItem = new IndexItem( ipid, treeKey );
// Prune out entries that are not for this PeerID
HashMap<Long, IndexItem> pitems = addGcItem( idxItem );
// Update or add the peerid removal index entry
pitems = (HashMap<Long, IndexItem>) this.peerRemovalIndex.get( pidString );
if ( null != pitems ) {
this.peerRemovalIndex.remove( pidString );
} else {
pitems = new HashMap<Long, IndexItem>( 1 );
}
pitems.put( idxItem.getId( ), idxItem );
this.peerRemovalIndex.put( pidString, pitems );
}
// Less efficient Gc item removal using enumeration of gcItems
private boolean removeGcItem( PeerIDItem piitem, String peerIDString ) {
boolean ret = false;
Long oldExpiry = Long.valueOf( piitem.getExpiry( ) );
// Remove from current position in the expire index
HashMap gcItems = (HashMap) this.expiryIndex.get( oldExpiry );
if( null != gcItems ){
Long id = null;
for ( Object iitemObj : gcItems.values( ) ) {
IndexItem iitem = (IndexItem) iitemObj;
if ( iitem.getIpid( ).getPeerid( ).getUniqueValue( ).toString( ).equals( peerIDString ) ) {
id = iitem.getId( );
break;
}
}
if ( null != id ) {
if ( null != gcItems.remove( id ) ) {
ret = true;
}
}
if ( gcItems.isEmpty( ) ) {
this.expiryIndex.remove( oldExpiry );
}
}
return ret;
}
private boolean removeGcItem( IndexItem iitem ) {
boolean ret = false;
Long oldExpiry = Long.valueOf( iitem.getIpid( ).getExpiry( ) );
// Remove from current position in the expire index
HashMap gcItems = (HashMap) this.expiryIndex.get( oldExpiry );
if ( null != gcItems ) {
gcItems.remove( iitem.getId( ) );
ret = true;
if ( gcItems.isEmpty( ) ) {
this.expiryIndex.remove( oldExpiry );
}
}
return ret;
}
private HashMap<Long, IndexItem> addGcItem( IndexItem iitem ) {
Long expiryKey = Long.valueOf( iitem.getIpid( ).getExpiry( ) );
HashMap gcItems = (HashMap) this.expiryIndex.get( expiryKey );
if ( null == gcItems ) {
gcItems = new HashMap( 1 );
this.expiryIndex.put( expiryKey, gcItems );
}
gcItems.put( iitem.getId( ), iitem );
return gcItems;
}
private static class PeerIDItem {
private PeerID peerid;
private long expiry;
PeerIDItem( PeerID peerid, long ttl ) {
this.peerid = peerid;
this.expiry = TimeUtils.toAbsoluteTimeMillis( ttl );
}
public PeerID getPeerid( ) {
return peerid;
}
public long getExpiry( ) {
return expiry;
}
public void setExpiry( long expiry ) {
this.expiry = expiry;
}
Entry toEntry( ) {
return new Entry( this.peerid, this.expiry );
}
}
private static class IndexItem {
private Long id;
private String treeKey;
PeerIDItem ipid;
public IndexItem( PeerIDItem ipid, String treeKey ) {
this.ipid = ipid;
this.id = InMemorySrdiIndexBackend.counter++;
this.treeKey = treeKey;
}
public String getTreeKey( ) {
return treeKey;
}
public Long getId( ) {
return id;
}
public PeerIDItem getIpid( ) {
return ipid;
}
}
}