package dk.statsbiblioteket.medieplatform.autonomous; import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.slf4j.Logger; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; /** * Implementation of the {@link EventTrigger} interface using SBOI summa index and DOMS. * Uses the SolrJConnector to query the summa instance for items, and REST to get batch details from DOMS. */ public class SBOIEventIndex<T extends Item> implements EventTrigger<T> { public static final String UUID = "item_uuid"; private static Logger log = org.slf4j.LoggerFactory.getLogger(SBOIEventIndex.class); protected final PremisManipulatorFactory<T> premisManipulatorFactory; protected final DomsEventStorage<T> domsEventStorage; protected final HttpSolrServer summaSearch; protected final int pageSize; public SBOIEventIndex(String summaLocation, PremisManipulatorFactory<T> premisManipulatorFactory, DomsEventStorage<T> domsEventStorage, int pageSize) throws MalformedURLException { this.premisManipulatorFactory = premisManipulatorFactory; this.domsEventStorage = domsEventStorage; this.pageSize = pageSize; summaSearch = new SolrJConnector(summaLocation).getSolrServer(); } @Override public Iterator<T> getTriggeredItems(Query<T> query) throws CommunicationException { Iterator<T> sboiItems = search(true, query); ArrayList<T> result = new ArrayList<>(); while (sboiItems.hasNext()) { T next = sboiItems.next(); if (match(next, query)) { result.add(next); } } return result.iterator(); } /** * Check that the item matches the requirements expressed in the three lists * * @param item the item to check * @param query query that must be fulfilled * * @return true if the item match all requirements */ protected boolean match(T item, Query<T> query) { Set<String> existingEvents = new HashSet<>(); Set<String> successEvents = new HashSet<>(); Set<String> oldEvents = new HashSet<>(); for (Event event : filterNewestEvent(item.getEventList())) { existingEvents.add(event.getEventID()); if (event.isSuccess()) { successEvents.add(event.getEventID()); } if (item.getLastModified() != null) { if (!event.getDate().after(item.getLastModified())) { oldEvents.add(event.getEventID()); } } } final boolean successEventsGood = successEvents.containsAll(query.getPastSuccessfulEvents()); boolean oldEventsGood = true; for (String oldEvent : query.getOldEvents()) { oldEventsGood = oldEventsGood && (oldEvents.contains(oldEvent) || !existingEvents.contains(oldEvent)); } boolean futureEventsGood = Collections.disjoint(existingEvents, query.getFutureEvents()); //TODONT we do not check for types for now return successEventsGood && oldEventsGood && futureEventsGood && (query.getItems() .isEmpty() || query.getItems() .contains(item)); } /** * Given a list of events, return only the newest event. * @param eventList A list of events to filter. * @return A list containing only the newest event. */ protected List<Event> filterNewestEvent(List<Event> eventList) { Map<String, Event> result = new HashMap<>(); for (Event event : eventList) { Event previousEvent = result.get(event.getEventID()); if (previousEvent == null || previousEvent.getDate().before(event.getDate())) { result.put(event.getEventID(), event); } } return new ArrayList<>(result.values()); } /** * Perform a search for items matching the given criteria * * @return An iterator over the found items * @throws CommunicationException if the communication failed */ public Iterator<T> search(boolean details, Query<T> query) throws CommunicationException { return search(details, toQueryString(query)); } public Iterator<T> search(boolean details, String freeFormSearchString) throws CommunicationException { return new SolrProxyIterator<>(freeFormSearchString,details,summaSearch,premisManipulatorFactory,domsEventStorage,pageSize); } protected static String spaced(String string) { return " " + string.trim() + " "; } protected static String quoted(String string) { return "\"" + string.replaceAll("\"","\\\"") + "\""; } protected static String anded(List<String> events) { StringBuilder result = new StringBuilder(); for (String event : events) { result.append(" AND ").append(event); } return result.toString(); } /** * Converts the query to a solr query string. * <ul> * * <li>The first part of the query is the Items, ie. the set of items which constrain the result set *</li><li> * The next part is the success events. Items must have these events with outcome success *</li><li> * The next part is the future events. Items must not have these events in with any outcome. *</li><li> * The next part is the old events. Items must either not have these events, or must have these events and must have received an update since this event was registered *</li><li> * The next part is the item types. These are the content models that the items must have. This is not about the * events at all, but about the types of items that can be returned. * </li> *</ul> * * @param query the query * @return the query string */ protected String toQueryString(Query<T> query) { String base = based(); String itemsString = ""; if (!query.getItems().isEmpty()) { itemsString = getResultRestrictions(query.getItems()); } List<String> events = new ArrayList<>(); for (String successfulPastEvent : query.getPastSuccessfulEvents()) { events.add(String.format(" +success_event:%1$s ", quoted(successfulPastEvent))); } for (String oldEvents : query.getOldEvents()) { events.add(String.format(" ( ( +old_event:%1$s ) OR ( -event:%1$s ) ) ", quoted(oldEvents))); } for (String futureEvent : query.getFutureEvents()) { events.add(String.format(" -event:%1$s ", quoted(futureEvent))); } for (String type : query.getTypes()) { events.add(String.format(" +item_model:%1$s ", quoted(type))); } return base + itemsString + anded(events); } protected String based() { return spaced("recordBase:doms_sboiCollection"); } protected String getResultRestrictions(Collection<T> items) { StringBuilder itemsString = new StringBuilder(); itemsString.append(" AND ( "); boolean first = true; for (Item item : items) { if (first) { first = false; } else { itemsString.append(" OR "); } itemsString.append(String.format(" ( +"+UUID+":%1$s ) ",quoted(item.getDomsID()))); } itemsString.append(" ) "); return itemsString.toString(); } }