/*
* Copyright 2012 Janrain, 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.
*/
package com.janrain.backplane.config;
import com.janrain.backplane.config.dao.ConfigDAOs;
import com.janrain.backplane.config.model.ServerConfigFields;
import com.janrain.backplane.dao.redis.RedisMessageProcessor;
import com.janrain.backplane.server1.dao.BP1DAOs;
import com.janrain.backplane.server1.dao.redis.RedisBackplane1DualFormatMessageProcessor;
import com.janrain.backplane.server2.dao.BP2DAOs;
import com.janrain.backplane.server2.model.Backplane2Message;
import com.janrain.backplane.server2.model.Backplane2MessageFields;
import com.janrain.commons.util.AwsUtility;
import com.janrain.commons.util.Pair;
import com.janrain.redis.Redis;
import com.netflix.curator.framework.CuratorFramework;
import com.netflix.curator.framework.CuratorFrameworkFactory;
import com.netflix.curator.framework.recipes.leader.LeaderSelector;
import com.netflix.curator.framework.recipes.leader.LeaderSelectorListener;
import com.netflix.curator.retry.ExponentialBackoffRetry;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.reporting.ConsoleReporter;
import com.yammer.metrics.reporting.GraphiteReporter;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Holds configuration settings for the Backplane server
*
* @author Jason Cowley, Johnny Bufu
*/
@Scope(value="singleton")
public class BackplaneConfig {
// - PUBLIC
/**
* @return the debugMode
*/
public static boolean isDebugMode() {
return ConfigDAOs.serverConfigDao().oneServerConfig().get().isDebugMode();
}
/**
* @return the server default max message value per channel
* @throws SimpleDBException
*/
public static long getDefaultMaxMessageLimit() {
Long max = Long.valueOf(ConfigDAOs.serverConfigDao().oneServerConfig().get().get(ServerConfigFields.DEFAULT_MESSAGES_MAX()).get());
return max == null ? BackplaneConfig.BP_MAX_MESSAGES_DEFAULT : max;
}
public static boolean isLeaderDisabled() {
// skip DAO layer, not so crazy about editing serialized streams for debug, yay FED-76
return isDebugMode() && Redis.getInstance().get(EC2InstanceId) != null;
}
public static Throwable getDebugException(Throwable e) {
return isDebugMode() ? e: null;
}
/**
* Retrieve the server instance id Amazon assigned
* @return
*/
public static String getEC2InstanceId() {
return EC2InstanceId;
}
public static void addToBackgroundServices(String workerName, ScheduledExecutorService messageWorkerTask) {
backgroundServices.put(workerName, messageWorkerTask);
}
public String getBuildVersion() {
return buildProperties.getProperty(BUILD_VERSION_PROPERTY);
}
// - PRIVATE
private static final Logger logger = Logger.getLogger(BackplaneConfig.class);
private static final String BUILD_PROPERTIES = "/build.properties";
private static final String BUILD_VERSION_PROPERTY = "build.version";
private static final Properties buildProperties = new Properties();
private static final long BP_MAX_MESSAGES_DEFAULT = 100;
private static final Map<String, ExecutorService> backgroundServices = new HashMap<String, ExecutorService>();
// Amazon specific instance-id value
private static String EC2InstanceId = AwsUtility.retrieveEC2InstanceId();
@SuppressWarnings({"UnusedDeclaration"})
private BackplaneConfig() {
ConsoleReporter.enable(10, TimeUnit.MINUTES);
// Dump metrics to graphite server
String graphiteServer = System.getProperty(SystemProperties.GRAPHITE_SERVER());
if (StringUtils.isNotBlank(graphiteServer)) {
try {
String args[] = graphiteServer.split(":");
String server = args[0];
int port = Integer.parseInt(args[1]);
GraphiteReporter.enable(10, TimeUnit.SECONDS, server, port, SystemProperties.machineName().replace(".","_") + "_" + SystemProperties.INSTANCE_ID());
logger.info("Graphite server enabled at " + graphiteServer);
} catch (Exception e) {
logger.warn("could not enable Graphite from " + graphiteServer + " must be in the form SERVER:PORT");
}
}
try {
buildProperties.load(BackplaneConfig.class.getResourceAsStream(BUILD_PROPERTIES));
//assert(StringUtils.isNotBlank(getEncryptionKey()));
} catch (Exception e) {
String err = "Error loading build properties from " + BUILD_PROPERTIES;
logger.error(err, e);
throw new RuntimeException(err, e);
}
logger.info("Configured Backplane Server instance: " + SystemProperties.INSTANCE_ID());
}
private Pair<String, ExecutorService> createPingTask() {
final String label = "redis/jedis";
ScheduledExecutorService ping = Executors.newScheduledThreadPool(1);
ping.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
com.janrain.redis.Redis.getInstance().ping(label);
}
}, 30, 10, TimeUnit.SECONDS);
return new Pair<String, ExecutorService>(label, ping);
}
private void addTask(Map<String, ExecutorService> backgroundServices, Pair<String, ExecutorService> nameAndService) {
backgroundServices.put(nameAndService.getLeft(), nameAndService.getRight());
}
@PostConstruct
private void init() {
addTask(backgroundServices, createPingTask());
// todo: replace with this after transition to new serialization is complete
//initZk("/v1_worker", new RedisMessageProcessor<Backplane1MessageFields.EnumVal, Backplane1Message>(BP1DAOs.messageDao()));
initZk("/v1_worker", new RedisBackplane1DualFormatMessageProcessor(BP1DAOs.messageDao()));
initZk("/v2_worker", new RedisMessageProcessor<Backplane2MessageFields.EnumVal, Backplane2Message>(BP2DAOs.messageDao()));
}
private void initZk(String leaderPath, LeaderSelectorListener listener) {
try {
String zkServerConfig = System.getProperty(SystemProperties.ZOOKEEPER_SERVERS());
if (StringUtils.isEmpty(zkServerConfig)) {
logger.error("Cannot find configuration entry for ZooKeeper server ('" + SystemProperties.ZOOKEEPER_SERVERS() + "' system property)" );
System.exit(1);
}
CuratorFramework client = CuratorFrameworkFactory.newClient(zkServerConfig, new ExponentialBackoffRetry(50, 20));
client.start();
LeaderSelector leaderSelector = new LeaderSelector(client, leaderPath, listener);
leaderSelector.autoRequeue();
leaderSelector.start();
com.janrain.redis.Redis.getInstance().setActiveRedisInstance(client);
} catch (Exception e) {
logger.error(e);
}
}
@PreDestroy
private void cleanup() {
Metrics.shutdown();
for (Map.Entry<String, ExecutorService> serviceEntry : backgroundServices.entrySet()) {
shutdownExecutor(serviceEntry.getKey(), serviceEntry.getValue());
}
}
private void shutdownExecutor(String serviceName, ExecutorService executor) {
try {
executor.shutdown();
if (executor.awaitTermination(10, TimeUnit.SECONDS)) {
logger.info(serviceName + " background thread shutdown properly");
} else {
executor.shutdownNow();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
logger.error(serviceName + " background thread did not terminate");
}
}
} catch (InterruptedException e) {
logger.error(serviceName + " termination threw an exception", e);
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}