package won.bot.framework.eventbot.action.impl.factory;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Resource;
import won.bot.framework.bot.context.FactoryBotContextWrapper;
import won.bot.framework.component.needproducer.NeedProducer;
import won.bot.framework.eventbot.EventListenerContext;
import won.bot.framework.eventbot.action.BaseEventBotAction;
import won.bot.framework.eventbot.action.impl.MultipleActions;
import won.bot.framework.eventbot.action.impl.PublishEventAction;
import won.bot.framework.eventbot.action.impl.counter.*;
import won.bot.framework.eventbot.action.impl.needlifecycle.AbstractCreateNeedAction;
import won.bot.framework.eventbot.action.impl.trigger.*;
import won.bot.framework.eventbot.action.impl.wonmessage.execCommand.ExecuteCreateNeedCommandAction;
import won.bot.framework.eventbot.action.impl.wonmessage.execCommand.LogMessageCommandFailureAction;
import won.bot.framework.eventbot.bus.EventBus;
import won.bot.framework.eventbot.event.Event;
import won.bot.framework.eventbot.event.impl.command.MessageCommandEvent;
import won.bot.framework.eventbot.event.impl.command.MessageCommandFailureEvent;
import won.bot.framework.eventbot.event.impl.command.MessageCommandResultEvent;
import won.bot.framework.eventbot.event.impl.command.create.CreateNeedCommandEvent;
import won.bot.framework.eventbot.event.impl.command.create.CreateNeedCommandFailureEvent;
import won.bot.framework.eventbot.event.impl.command.create.CreateNeedCommandSuccessEvent;
import won.bot.framework.eventbot.event.impl.factory.FactoryNeedCreationSkippedEvent;
import won.bot.framework.eventbot.event.impl.factory.InitFactoryFinishedEvent;
import won.bot.framework.eventbot.event.impl.factory.StartFactoryNeedCreationEvent;
import won.bot.framework.eventbot.event.impl.lifecycle.InitializeEvent;
import won.bot.framework.eventbot.event.impl.needlifecycle.NeedProducerExhaustedEvent;
import won.bot.framework.eventbot.filter.impl.TargetCounterFilter;
import won.bot.framework.eventbot.listener.EventListener;
import won.bot.framework.eventbot.listener.impl.ActionOnEventListener;
import won.bot.framework.eventbot.listener.impl.ActionOnFirstEventListener;
import won.protocol.util.WonRdfUtils;
import java.net.URI;
import java.time.Duration;
/**
* Action that creates all needs from the needproducer and publishes the InitFactoryFinishedEvent once it is
* completed
*/
public class InitFactoryAction extends AbstractCreateNeedAction {
private static int FACTORYNEEDCREATION_DURATION_INMILLIS = 100;
private int targetInFlightCount;
private int maxInFlightCount;
private final Counter needCreationStartedCounter = new CounterImpl("creationStarted");
private final Counter needCreationSuccessfulCounter = new CounterImpl("needsCreated");
private final Counter needCreationSkippedCounter = new CounterImpl("needCreationSkipped");
private final Counter needCreationFailedCounter = new CounterImpl("needCreationFailed");
private final Counter messagesInFlightCounter = new CounterImpl("messagesInflightCounter");
public InitFactoryAction(EventListenerContext eventListenerContext, URI... facets) {
this(eventListenerContext, 30, 50, facets);
}
public InitFactoryAction(EventListenerContext eventListenerContext, int targetInFlightCount, int maxInFlightCount, URI... facets) {
super(eventListenerContext, null, false, false, facets);
this.targetInFlightCount = targetInFlightCount;
this.maxInFlightCount = maxInFlightCount;
}
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
if(!(event instanceof InitializeEvent) || !(getEventListenerContext().getBotContextWrapper() instanceof FactoryBotContextWrapper)) {
logger.error("InitFactoryAction can only handle InitializeEvent with FactoryBotContextWrapper");
return;
}
final boolean usedForTesting = this.usedForTesting;
final boolean doNotMatch = this.doNotMatch;
EventListenerContext ctx = getEventListenerContext();
EventBus bus = ctx.getEventBus();
FactoryBotContextWrapper botContextWrapper = (FactoryBotContextWrapper) ctx.getBotContextWrapper();
//create a targeted counter that will publish an event when the target is reached
//in this case, 0 unfinished need creations means that all needs were created
final TargetCounterDecorator creationUnfinishedCounter = new TargetCounterDecorator(ctx, new CounterImpl("creationUnfinished"), 0);
BotTrigger createFactoryNeedTrigger = new BotTrigger(ctx, Duration.ofMillis(FACTORYNEEDCREATION_DURATION_INMILLIS));
createFactoryNeedTrigger.activate();
bus.subscribe(StartFactoryNeedCreationEvent.class, new ActionOnFirstEventListener(ctx, new PublishEventAction(ctx, new StartBotTriggerCommandEvent(createFactoryNeedTrigger))));
bus.subscribe(BotTriggerEvent.class, new ActionOnTriggerEventListener(ctx, createFactoryNeedTrigger, new BaseEventBotAction(ctx) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
if(isTooManyMessagesInFlight(messagesInFlightCounter)){
return;
}
adjustTriggerInterval(createFactoryNeedTrigger, messagesInFlightCounter);
NeedProducer needProducer = ctx.getNeedProducer(); //defined via spring
Model model = needProducer.create();
if(model == null && needProducer.isExhausted()) {
bus.publish(new NeedProducerExhaustedEvent());
bus.unsubscribe(executingListener);
return;
}
URI needUriFromProducer = null;
Resource needResource = WonRdfUtils.NeedUtils.getNeedResource(model);
if(needResource.isURIResource()){
needUriFromProducer = URI.create(needResource.getURI().toString());
}
if(needUriFromProducer != null){
URI needURI = botContextWrapper.getURIFromInternal(needUriFromProducer);
if(needURI != null){
bus.publish(new FactoryNeedCreationSkippedEvent());
}else{
bus.publish(new CreateNeedCommandEvent(model, botContextWrapper.getFactoryListName(), usedForTesting, doNotMatch));
}
}
}
}));
bus.subscribe(CreateNeedCommandSuccessEvent.class,
new ActionOnEventListener(ctx,
new MultipleActions(ctx,
new DecrementCounterAction(ctx, creationUnfinishedCounter), //decrease the creationUnfinishedCounter
new IncrementCounterAction(ctx, needCreationSuccessfulCounter), //count a successful need creation
new BaseEventBotAction(ctx) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
if (event instanceof CreateNeedCommandSuccessEvent) {
CreateNeedCommandSuccessEvent needCreatedEvent = (CreateNeedCommandSuccessEvent) event;
botContextWrapper.addInternalIdToUriReference(needCreatedEvent.getNeedUriBeforeCreation(), needCreatedEvent.getNeedURI());
}
}
}
)
)
);
bus.subscribe(CreateNeedCommandEvent.class,
new ActionOnEventListener(ctx,
new MultipleActions(ctx,
new ExecuteCreateNeedCommandAction(ctx), //execute the need creation for the need in the event
new IncrementCounterAction(ctx, needCreationStartedCounter), //increase the needCreationStartedCounter to signal another pending creation
new IncrementCounterAction(ctx, creationUnfinishedCounter) //increase the creationUnfinishedCounter to signal another pending creation
)
)
);
//if a need is already created we skip the recreation of it and increase the needCreationSkippedCounter
bus.subscribe(FactoryNeedCreationSkippedEvent.class, new ActionOnEventListener(ctx, new IncrementCounterAction(ctx, needCreationSkippedCounter)));
//if a creation failed, we don't want to keep us from keeping the correct count
bus.subscribe(CreateNeedCommandFailureEvent.class,
new ActionOnEventListener(ctx,
new MultipleActions(ctx,
new DecrementCounterAction(ctx, creationUnfinishedCounter), //decrease the creationUnfinishedCounter
new IncrementCounterAction(ctx, needCreationFailedCounter) //count an unsuccessful need creation
)
)
);
//when the needproducer is exhausted, we stop the creator (trigger) and we have to wait until all unfinished need creations finish
//when they do, the InitFactoryFinishedEvent is published
bus.subscribe(NeedProducerExhaustedEvent.class,
new ActionOnFirstEventListener(ctx,
new MultipleActions(ctx,
new PublishEventAction(ctx, new StopBotTriggerCommandEvent(createFactoryNeedTrigger)),
new BaseEventBotAction(ctx) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
//when we're called, there probably are need creations unfinished, but there may not be
//a)
//first, prepare for the case when there are unfinished need creations:
//we register a listener, waiting for the unfinished counter to reach 0
EventListener waitForUnfinishedNeedsListener = new ActionOnFirstEventListener(ctx, new TargetCounterFilter(creationUnfinishedCounter), new PublishEventAction(ctx, new InitFactoryFinishedEvent()));
bus.subscribe(TargetCountReachedEvent.class, waitForUnfinishedNeedsListener);
//now, we can check if we've already reached the target
if (creationUnfinishedCounter.getCount() <= 0) {
//ok, turned out we didn't need that listener
bus.unsubscribe(waitForUnfinishedNeedsListener);
bus.publish(new InitFactoryFinishedEvent());
}
}
}
)
)
);
bus.subscribe(InitFactoryFinishedEvent.class, new ActionOnFirstEventListener(ctx, "factoryCreateStatsLogger", new BaseEventBotAction(ctx) {
@Override
protected void doRun(Event event, EventListener executingListener) throws Exception {
logger.info("FactoryNeedCreation finished: total:{}, successful: {}, failed: {}, skipped: {}",
new Object[]{
needCreationStartedCounter.getCount(),
needCreationSuccessfulCounter.getCount(),
needCreationFailedCounter.getCount(),
needCreationSkippedCounter.getCount()});
}
}));
//MessageInFlight counter handling *************************
bus.subscribe(MessageCommandEvent.class, new ActionOnEventListener(ctx,new IncrementCounterAction(ctx, messagesInFlightCounter)));
bus.subscribe(MessageCommandResultEvent.class, new ActionOnEventListener(ctx, new DecrementCounterAction(ctx, messagesInFlightCounter)));
bus.subscribe(MessageCommandFailureEvent.class, new ActionOnEventListener(ctx, new LogMessageCommandFailureAction(ctx))); //if we receive a message command failure, log it
//Start the need creation stuff
bus.publish(new StartFactoryNeedCreationEvent());
}
private boolean isTooManyMessagesInFlight(Counter messagesInFlightCounter) {
return messagesInFlightCounter.getCount() > maxInFlightCount;
}
private void adjustTriggerInterval(BotTrigger createConnectionsTrigger, Counter targetCounter) {
//change interval to achieve desired inflight count
int desiredInFlightCount = targetInFlightCount;
int inFlightCountDiff = targetCounter.getCount() - desiredInFlightCount;
double factor = (double) inFlightCountDiff / (double)desiredInFlightCount;
createConnectionsTrigger.changeIntervalByFactor(1 + 0.001 * factor);
}
}