package com.mongodb.hvdf.channels;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoClientURI;
import com.mongodb.hvdf.MongoBackedService;
import com.mongodb.hvdf.configuration.ChannelServiceConfiguration;
import com.mongodb.hvdf.services.ChannelService;
import com.mongodb.hvdf.services.ServiceImplementation;
import com.mongodb.hvdf.util.JSONParam;
import com.mongodb.hvdf.util.MongoDBCommands;
import com.yammer.dropwizard.config.Configuration;
@ServiceImplementation(
name = "DefaultChannelService",
dependencies = { },
configClass = ChannelServiceConfiguration.class)
public class DefaultChannelService extends MongoBackedService
implements ChannelService, ChannelTaskScheduler{
private static final String CHANNEL_CONFIG = "hvdf_channels_";
private final ReentrantReadWriteLock channelLock =
new ReentrantReadWriteLock();
private final Map<String, Channel> channelMap =
new HashMap<String, Channel>();
private final Map<String, Map<String, List<ScheduledFuture<?>>>> tasks =
new HashMap<String, Map<String, List<ScheduledFuture<?>>>>();
private final ScheduledExecutorService taskExecutor;
private final ChannelServiceConfiguration config;
public DefaultChannelService(
final MongoClientURI dbUri,
final ChannelServiceConfiguration config){
super(dbUri, config);
this.config = config;
if(this.config.channel_task_thread_pool_size > 0){
this.taskExecutor = Executors.newScheduledThreadPool(
this.config.channel_task_thread_pool_size);
}
else {
this.taskExecutor = null;
}
}
@Override
public Channel getChannel(String feedName, String channelName) {
// Take the read lock and see if the channel exists
Channel channel = null;
String fullName = feedName + "-" + channelName;
channelLock.readLock().lock();
try{
channel = channelMap.get(fullName);
}
finally{
channelLock.readLock().unlock();
}
if(channel == null){
DBObject channelConfig = getChannelConfiguration(feedName, channelName);
channelLock.writeLock().lock();
try{
// make sure it wasnt created between locks
channel = channelMap.get(fullName);
if(channel == null){
// if still isn't there, build it
DB database = getFeedDatabase(feedName);
channel = new SimpleChannel(database, feedName, channelName);
channel.configure(channelConfig, this);
channelMap.put(fullName, channel);
}
}
finally{
channelLock.writeLock().unlock();
}
}
return channel;
}
private DB getFeedDatabase(String feedName) {
// screech will need to manage having separate servers
// configurable for individual feeds, for now it uses
// the server configured for all of them.
return this.client.getDB(feedName);
}
private DBCollection getChannelConfigCollection(String feedName) {
DB configDb = this.client.getDB("config");
return configDb.getCollection(CHANNEL_CONFIG + feedName);
}
@Override
public DBObject getChannelConfiguration(String feedName, String channelName) {
DBCollection configColl = getChannelConfigCollection(feedName);
return configColl.findOne(new BasicDBObject("_id", channelName));
}
@Override
public void configureChannel(String feedName, String channelName,
JSONParam channelConfig) {
// Now store the channel config persistently
DBCollection configColl = getChannelConfigCollection(feedName);
BasicDBObject newConfig = new BasicDBObject();
newConfig.putAll(channelConfig.toDBObject());
newConfig.put("_id", channelName);
// NOTE : Switched to using a command for this update since we want
// to store index configuration natively, but they often contain periods
// in their field names. Using a command avoids the client side checking
// which prohibits this and allows use of the "config" db on the server.
//
// configColl.update(new BasicDBObject("_id", channelName), newConfig, true, false);
MongoDBCommands.update(configColl, new BasicDBObject("_id", channelName), newConfig, true, false);
}
@Override
public void shutdown(long timeout, TimeUnit unit) {
// If this instance is running tasks, then schedule it
if(this.taskExecutor != null){
this.taskExecutor.shutdown();
}
// TODO: Flush all channels to ensure any batching etc completes
// call to the channel service
super.shutdown(timeout, unit);;
}
@Override
public Configuration getConfiguration() {
return this.config;
}
@Override
public ScheduledFuture<?> scheduleTask(
String feedName, String channelName,
ChannelTask task, long msPeriod) {
// If this instance is running tasks, then schedule it
if(this.taskExecutor != null){
List<ScheduledFuture<?>> channelList = getChannelTaskList(feedName, channelName);
ScheduledFuture<?> handle = this.taskExecutor.scheduleWithFixedDelay(
task, msPeriod, msPeriod, TimeUnit.MILLISECONDS);
channelList.add(handle);
return handle;
}
return null;
}
@Override
public void cancelAllForChannel(String feedName, String channelName) {
List<ScheduledFuture<?>> channelList = getChannelTaskList(feedName, channelName);
for(ScheduledFuture<?> toCancel : channelList){
toCancel.cancel(false);
}
}
private List<ScheduledFuture<?>> getChannelTaskList(String feedName, String channelName){
Map<String, List<ScheduledFuture<?>>> feedList = this.tasks.get(feedName);
if(feedList == null){
feedList = new HashMap<String, List<ScheduledFuture<?>>>();
this.tasks.put(feedName, feedList);
}
List<ScheduledFuture<?>> channelList = feedList.get(channelName);
if(channelList == null){
channelList = new ArrayList<ScheduledFuture<?>>();
feedList.put(channelName, channelList);
}
return channelList;
}
}