package dials; import akka.actor.*; import akka.routing.RoundRobinPool; import com.codahale.metrics.Timer; import dials.execution.ExecutionContext; import dials.execution.ExecutionRegistry; import dials.filter.FilterData; import dials.messages.ContextualMessage; import dials.messages.FeatureStateRequestMessage; import dials.messages.FeatureStateResultMessage; import dials.model.FeatureModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import scala.concurrent.duration.Duration; import javax.persistence.PersistenceException; import java.util.concurrent.TimeUnit; import static com.codahale.metrics.MetricRegistry.name; public class Dials { private static Logger logger = LoggerFactory.getLogger(Dials.class); private static final int TIMEOUT = 100; private static final int ACTOR_COUNT = 5; private static ActorSystem system; private static DialsSystemConfiguration systemConfiguration; private static boolean initialized = false; protected static void init(DialsSystemConfiguration configuration) { systemConfiguration = configuration; system = ActorSystem.create("Dials"); system.actorOf(Props.create(ExecutionRegistry.class, configuration.getExecutionContextRecorder()) .withRouter(new RoundRobinPool(ACTOR_COUNT)), "ExecutionRegistry"); initialized = true; } public static boolean getState(String featureName) { return getState(featureName, FilterData.EMPTY_FILTER_DATA); } public static boolean getState(final String featureName, FilterData dynamicData) { if (!initialized) { logger.error("Dials system has not been initialized."); return false; } markMeter(name("Dials", featureName, "RequestAttemptMeter")); Timer.Context timer = systemConfiguration.getMetricRegistry() .timer(name("Dials", featureName, "RequestTimer")).time(); FeatureModel feature = getFeature(featureName); if (meetsFeatureStateRequestPrerequisites(feature)) { boolean state = performFeatureStateRequest(featureName, dynamicData); timer.stop(); return state; } timer.stop(); return false; } private static FeatureModel getFeature(String featureName) { FeatureModel feature = null; try { feature = systemConfiguration.getRepository().getFeature(featureName); } catch (PersistenceException e) { logger.warn("Requested feature does not yet exist."); } return feature; } private static boolean meetsFeatureStateRequestPrerequisites(FeatureModel feature) { if (feature == null) { return false; } if (!feature.getIsEnabled()) { logger.warn("Requested feature is disabled."); return false; } return true; } private static boolean performFeatureStateRequest(String featureName, FilterData dynamicData) { Inbox inbox = Inbox.create(system); ActorRef dispatcher = getNewDialsDispatcher(); sendFeatureStateRequest(inbox, featureName, dynamicData, dispatcher); boolean state = getFeatureStateResult(inbox); inbox.send(dispatcher, PoisonPill.getInstance()); if (state) { markMeter(name("Dials", featureName, "RequestExecutionMeter")); } return state; } private static void sendFeatureStateRequest(Inbox inbox, String featureName, FilterData dynamicData, ActorRef dispatcher) { FeatureStateRequestMessage message = new FeatureStateRequestMessage(dynamicData, new ContextualMessage( new ExecutionContext(featureName).addExecutionStep("Feature State Request Started"), systemConfiguration)); inbox.send(dispatcher, message); } private static boolean getFeatureStateResult(Inbox inbox) { boolean state = false; try { state = ((FeatureStateResultMessage) inbox.receive(Duration.create(TIMEOUT, TimeUnit.MILLISECONDS))).getState(); } catch (Exception e) { logger.warn("System timed out calculating feature state, will return false."); } return state; } public static void sendError(String featureName) { markMeter(name("Dials", featureName, "RequestErrorMeter")); systemConfiguration.getRepository().registerFeatureError(featureName); } private static ActorRef getNewDialsDispatcher() { return system.actorOf(Props.create(DialsFacilitator.class)); } public static void shutdown() { system.shutdown(); } private static void markMeter(String meter) { systemConfiguration.getMetricRegistry().meter(meter).mark(); } }