package org.limewire.mojito.db;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.concurrent.FutureEvent;
import org.limewire.concurrent.FutureEvent.Type;
import org.limewire.mojito.Context;
import org.limewire.mojito.concurrent.DHTFuture;
import org.limewire.mojito.concurrent.DHTFutureAdapter;
import org.limewire.mojito.result.StoreResult;
import org.limewire.mojito.routing.Contact;
import org.limewire.mojito.settings.DatabaseSettings;
/**
* Publishes {@link Storable} values in the DHT.
*/
public class StorablePublisher implements Runnable {
private static final Log LOG = LogFactory.getLog(StorablePublisher.class);
private final Context context;
private ScheduledFuture future;
private final PublishTask publishTask = new PublishTask();
public StorablePublisher(Context context) {
this.context = context;
}
/**
* Starts the <code>DHTValuePublisher</code>.
*/
public void start() {
synchronized (publishTask) {
if (future == null) {
long delay = DatabaseSettings.STORABLE_PUBLISHER_PERIOD.getValue();
long initialDelay = delay;
future = context.getDHTExecutorService()
.scheduleWithFixedDelay(this, initialDelay, delay, TimeUnit.MILLISECONDS);
}
}
}
/**
* Stops the <code>DHTValuePublisher</code>.
*/
public void stop() {
synchronized (publishTask) {
if (future != null) {
future.cancel(true);
future = null;
publishTask.stop();
}
}
}
public void run() {
// Do not publish values if we're not bootstrapped!
if (context.isBootstrapped()
&& !context.isBootstrapping()) {
if (publishTask.isDone()) {
if (LOG.isInfoEnabled()) {
LOG.info(context.getName() + " begins with publishing");
}
publishTask.start();
}
} else {
if (LOG.isInfoEnabled()) {
LOG.info(context.getName() + " is not bootstrapped");
}
publishTask.stop();
}
}
/**
* Publishes DHTValue(s) one-by-one by going
* through a List of DHTValues. Every time a store finishes, it
* continues with the next DHTValue until all DHTValues have
* been republished
*/
private class PublishTask {
private Iterator<Storable> values = null;
private DHTFuture<StoreResult> future = null;
/**
* Stops the PublishTask
*/
public synchronized void stop() {
if (future != null) {
future.cancel(true);
future = null;
}
values = null;
}
/**
* Returns whether or not the PublishTask is done
*/
public synchronized boolean isDone() {
return values == null || !values.hasNext();
}
/**
* Starts the PublishTask.
*/
public synchronized void start() {
assert (isDone());
StorableModelManager modelManager = context.getStorableModelManager();
Collection<Storable> valuesToPublish = modelManager.getStorables();
if (valuesToPublish == null) {
valuesToPublish = Collections.emptyList();
}
if (LOG.isInfoEnabled()) {
LOG.info(context.getName() + " has "
+ valuesToPublish.size() + " DHTValues to process");
}
values = valuesToPublish.iterator();
next();
}
/**
* Publishes the next <code>DHTValue</code>.
*/
private synchronized boolean next() {
if (isDone()) {
if (LOG.isInfoEnabled()) {
LOG.info(context.getName() + " is done with publishing");
}
return false;
}
while(values.hasNext()) {
Storable storable = values.next();
if (publish(storable)) {
return true;
}
}
return false;
}
/**
* Publishes or expires the given <code>DHTValue</code>.
*/
private boolean publish(Storable storable) {
// Check if value is still in DB because we're
// working with a copy of the Collection.
future = context.store(DHTValueEntity.createFromStorable(context, storable));
future.addFutureListener(new StoreResultHandler(storable));
return true;
}
}
private class StoreResultHandler extends DHTFutureAdapter<StoreResult> {
private final Storable storable;
private StoreResultHandler(Storable storable) {
this.storable = storable;
}
@Override
protected void operationComplete(FutureEvent<StoreResult> event) {
FutureEvent.Type type = event.getType();
if (type == Type.SUCCESS) {
handleSuccess(event.getResult());
} else {
if (!publishTask.next()
|| type == Type.CANCELLED) {
publishTask.stop();
}
}
}
private void handleSuccess(final StoreResult result) {
if (LOG.isInfoEnabled()) {
Collection<? extends Contact> locations = result.getLocations();
if (!locations.isEmpty()) {
LOG.info(result);
} else {
LOG.info("Failed to store " + result.getValues());
}
}
storable.handleStoreResult(result);
context.getDHTExecutorService().execute(new Runnable() {
public void run() {
context.getStorableModelManager().handleStoreResult(storable, result);
}
});
if (!publishTask.next()) {
publishTask.stop();
}
}
}
}