package won.matcher.solr.actor;
import akka.actor.ActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import akka.actor.UntypedActor;
import akka.cluster.pubsub.DistributedPubSub;
import akka.cluster.pubsub.DistributedPubSubMediator;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import scala.concurrent.duration.Duration;
import won.matcher.service.common.event.*;
import won.matcher.service.common.spring.SpringExtension;
import won.matcher.solr.config.SolrMatcherConfig;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/**
* Created by hfriedrich on 30.09.2015.
*
* Matcher actor that subscribes itself to the PubSub Topic to receive need events from the matching service
* and forwards them to the actual matcher implementation (e.g. SolrMatcherActor) for hint generation.
* Then gets back the hints from the matcher implementation and publishes them to the PubSub Topic of hints.
*
*/
@Component
@Scope("prototype")
public class MatcherPubSubActor extends UntypedActor
{
private LoggingAdapter log = Logging.getLogger(getContext().system(), this);
private ActorRef pubSubMediator;
private ActorRef matcherActor;
@Autowired
private SolrMatcherConfig config;
private static final String TICK = "tick";
private static final String APP_STATE_PROPERTIES_FILE_NAME = "state.config.properties";
private static final String LAST_SEEN_NEED_DATE_PROPERTY_NAME = "lastSeenNeedDate";
private boolean needsUpdateRequestReceived = false;
private Properties appStateProps = new Properties();
@Override
public void preStart() throws IOException {
// subscribe to need events
pubSubMediator = DistributedPubSub.get(getContext().system()).mediator();
pubSubMediator.tell(new DistributedPubSubMediator.Subscribe(NeedEvent.class.getName(), getSelf()), getSelf());
// create the querying and indexing actors that do the actual work
if (config.isMonitoringEnabled()) {
matcherActor = getContext().actorOf(SpringExtension.SpringExtProvider.get(
getContext().system()).fromConfigProps(SolrMonitoringMatcherActor.class), "SolrMatcherPool");
} else {
matcherActor = getContext().actorOf(SpringExtension.SpringExtProvider.get(
getContext().system()).fromConfigProps(SolrMonitoringMatcherActor.class), "SolrMatcherPool");
}
// Create a scheduler to request missing need events from matching service while this matcher was not available
getContext().system().scheduler().schedule(
Duration.create(30, TimeUnit.SECONDS), Duration.create(60, TimeUnit.SECONDS), getSelf(), TICK,
getContext().dispatcher(), null);
// read properties file that has the lastSeenNeedDate
FileInputStream in = null;
try {
in = new FileInputStream(APP_STATE_PROPERTIES_FILE_NAME);
appStateProps.load(in);
log.info("loaded properties file {}, property '{}' is set to " + appStateProps.getProperty
(LAST_SEEN_NEED_DATE_PROPERTY_NAME), APP_STATE_PROPERTIES_FILE_NAME, LAST_SEEN_NEED_DATE_PROPERTY_NAME);
} catch (FileNotFoundException e) {
log.info("properties file {} not found, create file", APP_STATE_PROPERTIES_FILE_NAME);
} catch (IOException e) {
log.error("cannot read properties file {}", APP_STATE_PROPERTIES_FILE_NAME);
throw e;
} finally {
if (in != null) {
in.close();
}
if (appStateProps.getProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME) == null) {
appStateProps.setProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME, String.valueOf(-1));
saveLastSeenNeedDate();
}
}
}
public void saveLastSeenNeedDate() throws IOException {
FileOutputStream out = null;
try {
out = new FileOutputStream(APP_STATE_PROPERTIES_FILE_NAME);
appStateProps.store(out, null);
} catch (IOException e) {
log.error("cannot write properties file {}", APP_STATE_PROPERTIES_FILE_NAME);
throw e;
} finally {
if (out != null) {
out.close();
}
}
}
@Override
public void onReceive(Object o) throws Exception {
if (o.equals(TICK)) {
if (!needsUpdateRequestReceived) {
// request missing need events from matching service while this matcher was not available
long lastSeenNeedDate = Long.valueOf(appStateProps.getProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME));
LoadNeedEvent loadNeedEvent;
if (lastSeenNeedDate == -1) {
// request the last one need event from matching service and accept every need event timestamp
loadNeedEvent = new LoadNeedEvent(1);
} else {
// request need events with date > last need event date
log.info("request missed needs from mataching service with crawl date > {}", lastSeenNeedDate);
loadNeedEvent = new LoadNeedEvent(lastSeenNeedDate, Long.MAX_VALUE);
}
pubSubMediator.tell(new DistributedPubSubMediator.Publish(
loadNeedEvent.getClass().getName(), loadNeedEvent), getSelf());
}
} else if (o instanceof NeedEvent) {
NeedEvent needEvent = (NeedEvent) o;
log.info("NeedEvent received: " + needEvent);
// save the last seen need date property after the needs are up to date with the matching service
if (needsUpdateRequestReceived) {
long lastSeenNeedDate = Long.valueOf(appStateProps.getProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME));
if (needEvent.getCrawlDate() > lastSeenNeedDate) {
appStateProps.setProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME, String.valueOf(needEvent.getCrawlDate()));
saveLastSeenNeedDate();
}
}
matcherActor.tell(needEvent, getSelf());
} else if (o instanceof BulkNeedEvent) {
// receiving a bulk need event means this is the answer for the request of need updates
// there could arrive several of these bulk events
needsUpdateRequestReceived = true;
BulkNeedEvent bulkNeedEvent = (BulkNeedEvent) o;
log.info("BulkNeedEvent received with {} need events", bulkNeedEvent.getNeedEvents().size());
for (NeedEvent needEvent : ((BulkNeedEvent) o).getNeedEvents()) {
long lastSeenNeedDate = Long.valueOf(appStateProps.getProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME));
if (needEvent.getCrawlDate() > lastSeenNeedDate) {
appStateProps.setProperty(LAST_SEEN_NEED_DATE_PROPERTY_NAME, String.valueOf(needEvent.getCrawlDate()));
saveLastSeenNeedDate();
}
matcherActor.tell(needEvent, getSelf());
}
} else if (o instanceof HintEvent) {
HintEvent hintEvent = (HintEvent) o;
log.info("Publish hint event: " + hintEvent);
pubSubMediator.tell(new DistributedPubSubMediator.Publish(
hintEvent.getClass().getName(), hintEvent), getSelf());
} else if (o instanceof BulkHintEvent) {
BulkHintEvent bulkHintEvent = (BulkHintEvent) o;
log.info("Publish bulk hint event: " + bulkHintEvent);
pubSubMediator.tell(new DistributedPubSubMediator.Publish(
bulkHintEvent.getClass().getName(), bulkHintEvent), getSelf());
} else {
unhandled(o);
}
}
@Override
public SupervisorStrategy supervisorStrategy() {
SupervisorStrategy supervisorStrategy = new OneForOneStrategy(
0, Duration.Zero(), new Function<Throwable, SupervisorStrategy.Directive>()
{
@Override
public SupervisorStrategy.Directive apply(Throwable t) throws Exception {
log.warning("Actor encountered error: {}", t);
// default behaviour
return SupervisorStrategy.escalate();
}
});
return supervisorStrategy;
}
}