package won.matcher.solr.actor; import akka.actor.OneForOneStrategy; import akka.actor.SupervisorStrategy; import akka.actor.UntypedActor; import akka.event.Logging; import akka.event.LoggingAdapter; import akka.japi.Function; import com.github.jsonldjava.core.JsonLdError; import org.apache.jena.query.Dataset; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.params.SolrParams; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import scala.concurrent.duration.Duration; import won.matcher.service.common.event.BulkHintEvent; import won.matcher.service.common.event.BulkNeedEvent; import won.matcher.service.common.event.NeedEvent; import won.matcher.solr.config.SolrMatcherConfig; import won.matcher.solr.hints.HintBuilder; import won.matcher.solr.index.NeedIndexer; import won.matcher.solr.query.DefaultMatcherQueryExecuter; import won.matcher.solr.query.SolrMatcherQueryExecutor; import won.matcher.solr.query.TestMatcherQueryExecutor; import won.matcher.solr.query.factory.CreationDateQueryFactory; import won.matcher.solr.query.factory.DefaultNeedQueryFactory; import won.matcher.solr.query.factory.NeedStateQueryFactory; import won.protocol.util.NeedModelWrapper; import won.protocol.vocabulary.WON; import java.io.IOException; import java.time.temporal.ChronoUnit; /** * Siren/Solr based abstract matcher with all implementations for querying as well as indexing needs. */ @Component @Scope("prototype") public abstract class AbstractSolrMatcherActor extends UntypedActor { private LoggingAdapter log = Logging.getLogger(getContext().system(), this); @Autowired private SolrMatcherConfig config; @Autowired private HintBuilder hintBuilder; @Autowired private NeedIndexer needIndexer; @Autowired @Qualifier("defaultMatcherQueryExecuter") DefaultMatcherQueryExecuter defaultQueryExecuter; @Autowired TestMatcherQueryExecutor testQueryExecuter; @Override public void onReceive(final Object o) throws Exception { if (o instanceof NeedEvent) { NeedEvent needEvent = (NeedEvent) o; processNeedEvent(needEvent); } else if (o instanceof BulkNeedEvent) { log.info("received bulk need event, processing {} need events ...", ((BulkNeedEvent) o).getNeedEvents().size()); for (NeedEvent event : ((BulkNeedEvent) o).getNeedEvents()) { processNeedEvent(event); } } else { unhandled(o); } } protected void processNeedEvent(NeedEvent needEvent) throws IOException, SolrServerException, JsonLdError { log.info("Start processing need event {}", needEvent); // check if the need has doNotMatch flag, then do not use it for querying or indexing Dataset dataset = deserializeNeed(needEvent); NeedModelWrapper needModelWrapper = new NeedModelWrapper(dataset); if (needModelWrapper.hasFlag(WON.NO_HINT_FOR_ME) && needModelWrapper.hasFlag(WON.NO_HINT_FOR_COUNTERPART)) { log.info("Discarding received need due to flags won:NoHintForMe and won:NoHintForCounterpart: {}", needEvent); return; } // check if need is usedForTesting only boolean usedForTesting = needModelWrapper.hasFlag(WON.USED_FOR_TESTING); SolrMatcherQueryExecutor queryExecutor = (usedForTesting ? testQueryExecuter : defaultQueryExecuter); // default query matches content terms (of fields title, description and tags) with different weights // and gives an additional multiplicative boost for geographically closer needs DefaultNeedQueryFactory needQueryFactory = new DefaultNeedQueryFactory(dataset); String queryString = needQueryFactory.createQuery(); // add filters to the query: need status active and creation date overlap 1 month String[] filterQueries = new String[2]; filterQueries[0] = new NeedStateQueryFactory(dataset).createQuery(); filterQueries[1] = new CreationDateQueryFactory(dataset, 1, ChronoUnit.MONTHS).createQuery(); log.info("query Solr endpoint {} for need {}", config.getSolrEndpointUri(usedForTesting), needEvent.getUri()); SolrDocumentList docs = executeQuery( queryExecutor, queryString, null, filterQueries); if (docs != null) { BulkHintEvent events = produceHints(docs, needEvent, needModelWrapper); publishHints(events, needEvent); } else { log.warning("No results found for query of need ", needEvent); } indexNeedEvent(needEvent, dataset); } protected Dataset deserializeNeed(NeedEvent needEvent) throws IOException { Dataset dataset = needEvent.deserializeNeedDataset(); return dataset; } protected SolrDocumentList executeQuery(SolrMatcherQueryExecutor queryExecutor, String queryString, SolrParams params, String... filterQueries) throws IOException, SolrServerException { return queryExecutor.executeNeedQuery(queryString, params, filterQueries); } protected BulkHintEvent produceHints(SolrDocumentList docs, NeedEvent needEvent, NeedModelWrapper needModelWrapper) { BulkHintEvent bulkHintEvent = hintBuilder.generateHintsFromSearchResult(docs, needEvent, needModelWrapper); return bulkHintEvent; } protected void publishHints(BulkHintEvent bulkHintEvent, NeedEvent needEvent) { log.info("Create {} hints for need {}", bulkHintEvent.getHintEvents().size(), needEvent); if (bulkHintEvent.getHintEvents().size() != 0) { log.debug("Publish bulk hint event: " + bulkHintEvent); getSender().tell(bulkHintEvent, getSelf()); } } protected void indexNeedEvent(NeedEvent needEvent, Dataset dataset) throws IOException, JsonLdError { log.info("Add need event content {} to solr index", needEvent); needIndexer.index(dataset); } @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; } }