/*
* 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.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import net.jxta.impl.util.TimeUtils;
import net.jxta.impl.xindice.core.data.Key;
import net.jxta.logging.Logger;
import net.jxta.logging.Logging;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
/**
* Searchable store of records of all known peers who have broadcast advertisements that have not yet
* expired.
* <p>
* Internally, this is a wrapper around an {@link net.jxta.impl.cm.SrdiAPI} selected using the system property
* {@link #SRDI_INDEX_BACKEND_SYSPROP}. If no backend is specified through this system property, the default
* implementation specified by {@link #DEFAULT_SRDI_INDEX_BACKEND} is used.
*/
public class Srdi implements SrdiAPI {
public static final String SRDI_INDEX_BACKEND_SYSPROP = "net.jxta.impl.cm.Srdi.backend.impl";
public static final String DEFAULT_SRDI_INDEX_BACKEND = "net.jxta.impl.cm.InMemorySrdi";
public static final long DEFAULT_GC_INTERVAL = 10 * TimeUtils.AMINUTE;
public static final long NO_AUTO_GC = -1;
private final static transient Logger LOG = Logging.getLogger(Srdi.class.getName());
private SrdiAPI backend;
private ScheduledFuture<?> gcHandle;
private final ScheduledExecutorService scheduledExecutor;
/**
* Constructor for the Srdi
*
* @param group group
* @param indexName the index name
*/
public Srdi(PeerGroup group, String indexName) {
this(group, indexName, DEFAULT_GC_INTERVAL);
}
/**
* Construct a Srdi and starts a GC thread which runs every "interval"
* milliseconds
*
* @param interval the interval at which the gc will run in milliseconds
* @param group group context
* @param indexName Srdi name
*/
public Srdi(PeerGroup group, String indexName, long interval) {
this.scheduledExecutor = group.getTaskManager().getScheduledExecutorService();
if(System.getProperty(SRDI_INDEX_BACKEND_SYSPROP) == null) {
Logging.logCheckedConfig(LOG, "No Srdi implementation specified through system property - using default implementation");
}
String backendClassName = System.getProperty(SRDI_INDEX_BACKEND_SYSPROP, DEFAULT_SRDI_INDEX_BACKEND);
createBackend(backendClassName, group, indexName);
Logging.logCheckedInfo(LOG, "[", group.toString(), "] : Starting SRDI GC Thread for ", indexName);
startGC(interval);
}
private void startGC(long interval) {
if(interval <= 0) {
// automatic gc disabled
return;
}
gcHandle = scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
public void run() {
garbageCollect();
}
}, interval, interval, TimeUnit.MILLISECONDS);
}
private void createBackend(String backendClassName, PeerGroup group, String indexName) {
try {
Logging.logCheckedConfig(LOG, "Attempting to use Srdi backend class: ", backendClassName);
Class<? extends SrdiAPI> backendClass = getBackendClass();
Constructor<? extends SrdiAPI> constructor = backendClass.getConstructor(PeerGroup.class, String.class);
backend = (SrdiAPI) constructor.newInstance(group, indexName);
Logging.logCheckedConfig(LOG, "Srdi backend [", backendClassName, "] loaded successfully");
} catch (Exception e) {
Logging.logCheckedError(LOG, "Unable to construct SRDI Index backend [", backendClassName, "] specified by system property, constructing default\n", e);
backend = new XIndiceSrdi(group, indexName);
}
}
public Srdi(SrdiAPI backend, long gcInterval, ScheduledExecutorService scheduledExecutor) {
this.backend = backend;
this.scheduledExecutor = scheduledExecutor;
startGC(gcInterval);
}
protected String getBackendClassName() {
return backend.getClass().getName();
}
protected SrdiAPI getBackend() {
return backend;
}
/**
* add an index entry
*
* @param primaryKey primary key
* @param attribute Attribute String to query on
* @param value value of the attribute string
* @param expiration expiration associated with this entry relative time in
* milliseconds
* @param pid peerid reference
* @throws IOException if an error occurred storing the entry
*/
public synchronized void add(String primaryKey, String attribute, String value, PeerID pid, long expiration) {
try {
backend.add(primaryKey, attribute, value, pid, expiration);
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to write entry to backend\n", e);
}
}
/**
* retrieves a record
*
* @param pkey primary key
* @param skey secondary key
* @param value value
* @return List of Entry objects
*/
public List<Entry> getRecord(String pkey, String skey, String value) {
try {
return backend.getRecord(pkey, skey, value);
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to retrieve record from backend\n", e);
return new LinkedList<Entry>();
}
}
/**
* remove entries pointing to peer id from cache
*
* @param pid peer id to remove
*/
public synchronized void remove(PeerID pid) {
try {
backend.remove(pid);
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to remove record from backend\n", e);
}
}
/**
* Query Srdi
*
* @param attribute Attribute String to query on
* @param value value of the attribute string
* @return an enumeration of canonical paths
* @param primaryKey primary key
* @param threshold max number of results
*/
public synchronized List<PeerID> query(String primaryKey, String attribute, String value, int threshold) {
try {
return backend.query(primaryKey, attribute, value, threshold);
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to query backend for pk=[", primaryKey, "], attr=[", attribute, "], val=[", value, "], thresh=[", threshold, "]\n", e);
return new LinkedList<PeerID>();
}
}
/**
* Garbage Collect expired entries
*/
public void garbageCollect() {
try {
backend.garbageCollect();
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to garbage collect backend\n", e);
}
}
public void clear() {
try {
backend.clear();
} catch(IOException e) {
Logging.logCheckedWarning(LOG, "Failed to clear backend\n", e);
}
}
/**
* stop the current running thread
*/
public synchronized void stop() {
if(gcHandle != null) {
gcHandle.cancel(false);
}
backend.stop();
}
/**
* Flushes the Srdi directory for a specified group
* this method should only be called before initialization of a given group
* calling this method on a running group would have undefined results
*
* @param group group context
*/
public static void clearSrdi(PeerGroup group) {
Logging.logCheckedInfo(LOG, "Clearing SRDI for " + group.getPeerGroupName());
Class<? extends SrdiAPI> backendClass = getBackendClass();
try {
backendClass.getMethod("clearSrdi", PeerGroup.class).invoke(null, group);
} catch (Exception e) {
Logging.logCheckedWarning(LOG, "Failed to clear Srdi cache for peer group ", group.getPeerGroupName(), e);
}
}
/**
* Loads and checks the backend class specified by {@link #SRDI_INDEX_BACKEND_SYSPROP} conforms to
* the specification of Srdi, and provides the appropriate constructors and static methods.
*/
private static Class<? extends SrdiAPI> getBackendClass() {
String backendClassName = System.getProperty(SRDI_INDEX_BACKEND_SYSPROP, DEFAULT_SRDI_INDEX_BACKEND);
Class<?> backendClass;
try {
backendClass = Class.forName(backendClassName);
} catch(ClassNotFoundException e) {
Logging.logCheckedError(LOG, "Class specified for use as backend could not be found\n", e);
return getDefaultBackendClass();
}
Class<? extends SrdiAPI> backendClassChecked;
try {
backendClassChecked = backendClass.asSubclass(SrdiAPI.class);
} catch (ClassCastException e) {
Logging.logCheckedError(LOG, "Class specified for use as backend does not implement SrdiAPI\n", e);
return getDefaultBackendClass();
}
try {
backendClassChecked.getConstructor(PeerGroup.class, String.class);
} catch (Exception e) {
Logging.logCheckedError(LOG, "Class specified for use as backend does not provide accessible constructor which takes PeerGroup and String as parameters\n", e);
return getDefaultBackendClass();
}
try {
Method method = backendClassChecked.getMethod("clearSrdi", PeerGroup.class);
if((method.getModifiers() & Modifier.STATIC) == 0) {
Logging.logCheckedError(LOG, "Class specified for use as backend does not provide method clearSrdi as a static");
return getDefaultBackendClass();
}
} catch(Exception e) {
Logging.logCheckedError(LOG, "Class specified for use as backend does not provide accessible method clearSrdi which takes a PeerGroup\n", e);
return getDefaultBackendClass();
}
return backendClassChecked;
}
private static Class<? extends SrdiAPI> getDefaultBackendClass() {
try {
return Class.forName(DEFAULT_SRDI_INDEX_BACKEND).asSubclass(SrdiAPI.class);
} catch (ClassNotFoundException e) {
Logging.logCheckedError(LOG, "Could not load default backend for SrdiIndex\n", e);
throw new RuntimeException("Could not load default backend for SrdiIndex", e);
}
}
/**
* An entry in the index tables.
*/
public final static class Entry {
public final PeerID peerid;
public final long expiration;
/**
* Peer Pointer reference
*
* @param peerid PeerID for this entry
* @param expiration the expiration for this entry
*/
public Entry(PeerID peerid, long expiration) {
this.peerid = peerid;
this.expiration = expiration;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
return obj instanceof Entry && (peerid.equals(((Entry) obj).peerid));
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return peerid.hashCode();
}
/**
* Return the absolute time in milliseconds at which this entry will
* expire.
*
* @return The absolute time in milliseconds at which this entry will
* expire.
*/
public long getExpiration() {
return expiration;
}
/**
* Return {@code true} if this entry is expired.
*
* @return {@code true} if this entry is expired otherwise {@code false}.
*/
public boolean isExpired() {
return TimeUtils.timeNow() > expiration;
}
}
/**
* an SrdiIndexRecord wrapper
*/
public final static class SrdiIndexRecord {
public final Key key;
public final List<Entry> list;
/**
* Srdi record
*
* @param key record key
* @param list record entries
*/
public SrdiIndexRecord(Key key,List<Entry> list) {
this.key = key;
this.list = list;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
return obj instanceof SrdiIndexRecord && (key.equals(((SrdiIndexRecord) obj).key));
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return key.hashCode();
}
}
}