package org.atomnuke.fallout.context;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.atomnuke.Nuke;
import org.atomnuke.NukeEnvironment;
import org.atomnuke.atombus.config.model.Binding;
import org.atomnuke.atombus.config.model.MessageActor;
import org.atomnuke.atombus.config.model.MessageSource;
import org.atomnuke.atombus.config.model.Parameter;
import org.atomnuke.atombus.config.model.Parameters;
import org.atomnuke.fallout.config.server.ServerConfigurationHandler;
import org.atomnuke.plugin.InstanceContext;
import org.atomnuke.plugin.operation.OperationFailureException;
import org.atomnuke.sink.AtomSink;
import org.atomnuke.source.AtomSource;
import org.atomnuke.task.atom.AtomTask;
import org.atomnuke.task.context.AtomTaskContext;
import org.atomnuke.task.context.TaskContextImpl;
import org.atomnuke.task.manager.AtomTasker;
import org.atomnuke.util.TimeValueUtil;
import org.atomnuke.util.config.ConfigurationException;
import org.atomnuke.lifecycle.Reclaimable;
import org.atomnuke.service.introspection.ServicesInterrogator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author zinic
*/
public class FalloutContextImpl implements FalloutContext {
private static final Logger LOG = LoggerFactory.getLogger(FalloutContextImpl.class);
private final ServicesInterrogator servicesInterrogator;
private final NukeEnvironment nukeEnvironment;
private final Map<String, Binding> bindings;
private final Map<String, AtomTask> tasks;
private final ActorManager actorManager;
private final AtomTasker atomTasker;
public FalloutContextImpl(Nuke nukeReference, ServicesInterrogator servicesInterrogator) {
this.servicesInterrogator = servicesInterrogator;
this.nukeEnvironment = nukeReference.nukeEnvironment();
this.atomTasker = nukeReference.atomTasker();
actorManager = new ActorManager();
tasks = new HashMap<String, AtomTask>();
bindings = new HashMap<String, Binding>();
}
private static Map<String, String> parametersToMap(Parameters parameters) {
final Map<String, String> paramMap = new HashMap<String, String>();
if (parameters != null) {
for (Parameter param : parameters.getParam()) {
paramMap.put(param.getName(), param.getValue());
}
}
return paramMap;
}
private boolean isActorBoundAsSource(String id) {
for (Binding binding : bindings.values()) {
if (binding.getSourceActor().equals(id)) {
return true;
}
}
return false;
}
private boolean isActorBoundToAnySource(String id) {
for (Binding binding : bindings.values()) {
if (binding.getSinkActor().equals(id)) {
return true;
}
}
return false;
}
@Override
public void enlistActor(String name, InstanceContext<? extends Reclaimable> actor) {
// We found an actor!
LOG.debug("Found configuration for actor: " + name);
// Do we already have a living reference to this actor?
if (!actorManager.hasActor(name)) {
// No? Let's add one then.
actorManager.manageActor(name, (InstanceContext<Reclaimable>) actor);
} else {
// Debug log this case in case of problems
LOG.debug("Actor already registered. Ignoring this instance.");
}
}
@Override
public void process(ServerConfigurationHandler cfgHandler) throws ConfigurationException {
final List<Binding> bindingsToMerge = cfgHandler.getBindings();
final Set<String> bindingsToBreak = new HashSet<String>(bindings.keySet());
final Set<Binding> bindingsToAdd = new HashSet<Binding>();
for (Binding binding : bindingsToMerge) {
if (!bindingsToBreak.remove(binding.getId())) {
bindingsToAdd.add(binding);
}
}
for (Binding binding : bindingsToAdd) {
bind(cfgHandler, binding);
}
for (String breakId : bindingsToBreak) {
LOG.info("Breaking binding: " + breakId);
bindings.remove(breakId);
}
garbageCollect();
}
private void garbageCollect() {
final List<ActorEntry> garbageQueue = new LinkedList<ActorEntry>();
for (String id : new HashSet<String>(actorManager.actorNames())) {
// If this actor isn't bound to a source then it's doing nothing and should be removed
if (!isActorBoundAsSource(id) && !isActorBoundToAnySource(id)) {
retireActor(id, garbageQueue);
}
}
for (ActorEntry actorEntry : garbageQueue) {
actorEntry.cancel();
}
}
private void retireActor(String id, List<ActorEntry> garbageQueue) {
// Remove any lingering references - this might be a source
tasks.remove(id);
// Queue the actor for cancellation
final ActorEntry actor = actorManager.removeActor(id);
// If there's a cancellation remote, then this actor was initialized and needs to be garbage collected
if (actor != null) {
LOG.debug("Garbage collecting: " + actor);
if (actor.initialized()) {
garbageQueue.add(actor);
}
}
}
private void bind(ServerConfigurationHandler cfgHandler, Binding binding) throws ConfigurationException {
LOG.info("Binding source " + binding.getSourceActor() + " to sink " + binding.getSinkActor());
bind(getSourceTask(binding, cfgHandler), binding, cfgHandler);
}
private void bind(AtomTask source, Binding binding, ServerConfigurationHandler cfgHandler) throws ConfigurationException {
final MessageActor sinkMessageActorDescriptor = cfgHandler.findMessageActor(binding.getSinkActor());
final ActorEntry sinkActor = actorManager.getActor(binding.getSinkActor());
if (sinkActor == null || sinkMessageActorDescriptor == null) {
throw new ConfigurationException("Unable to locate actor, \"" + binding.getSinkActor() + "\" for usage as a sink.");
}
final Class instanceRefClass = sinkActor.instanceClass();
if (!AtomSink.class.isAssignableFrom(instanceRefClass)) {
throw new ConfigurationException("Actor, \"" + binding.getSinkActor() + "\" does not implement the AtomSink interface and can not be used as a sink.");
}
final AtomTaskContext taskContext = new TaskContextImpl(nukeEnvironment, LoggerFactory.getLogger(sinkMessageActorDescriptor.getId()),
parametersToMap(sinkMessageActorDescriptor.getParameters()), servicesInterrogator, atomTasker, sinkMessageActorDescriptor.getId());
try {
sinkActor.init(taskContext);
} catch (OperationFailureException ofe) {
LOG.error("Unable to initialize sink: " + sinkMessageActorDescriptor.getId() + " with href: " + sinkMessageActorDescriptor.getHref() + " - Reason: " + ofe.getMessage(), ofe);
}
actorManager.setCancellationRemote(binding.getSinkActor(), source.addSink((InstanceContext<AtomSink>) sinkActor.instanceContext()));
bindings.put(binding.getId(), binding);
}
private AtomTask getSourceTask(Binding binding, ServerConfigurationHandler cfgHandler) throws ConfigurationException {
AtomTask sourceTask = tasks.get(binding.getSourceActor());
return sourceTask != null ? sourceTask : registerTask(cfgHandler, binding);
}
private AtomTask registerTask(ServerConfigurationHandler cfgHandler, Binding binding) throws ConfigurationException {
final MessageActor sourceMessageActorDescriptor = cfgHandler.findMessageActor(binding.getSourceActor());
final MessageSource messageSourceDef = cfgHandler.findMessageSource(binding.getSourceActor());
if (messageSourceDef == null || sourceMessageActorDescriptor == null) {
throw new ConfigurationException("Actor, \"" + binding.getSinkActor() + "\" does not have a source definition within the configuration.");
}
final ActorEntry sourceActor = actorManager.getActor(sourceMessageActorDescriptor.getId());
if (sourceActor == null) {
throw new ConfigurationException("Unable to locate actor, \"" + sourceMessageActorDescriptor.getId() + "\" for usage as a source.");
}
// Reaching deep
final Class instanceRefClass = sourceActor.instanceClass();
if (!AtomSource.class.isAssignableFrom(instanceRefClass)) {
throw new ConfigurationException("Actor, \"" + sourceMessageActorDescriptor.getId() + "\" does not implement the AtomSource interface and can not be used as a source.");
}
final AtomTaskContext taskContext = new TaskContextImpl(nukeEnvironment, LoggerFactory.getLogger(sourceMessageActorDescriptor.getId()),
parametersToMap(sourceMessageActorDescriptor.getParameters()), servicesInterrogator, atomTasker, sourceMessageActorDescriptor.getId());
try {
sourceActor.init(taskContext);
} catch (OperationFailureException ofe) {
LOG.error("Unable to initialize source: " + sourceMessageActorDescriptor.getId() + " with href: " + sourceMessageActorDescriptor.getHref() + " - Reason: " + ofe.getMessage(), ofe);
}
final AtomTask sourceTask = atomTasker.follow((InstanceContext<AtomSource>) sourceActor.instanceContext(), TimeValueUtil.fromPollingInterval(messageSourceDef.getPollingInterval()));
tasks.put(sourceMessageActorDescriptor.getId(), sourceTask);
actorManager.setCancellationRemote(sourceMessageActorDescriptor.getId(), sourceTask.handle().cancellationRemote());
return sourceTask;
}
}