package com.psddev.cms.tool.search;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.common.params.CommonParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.psddev.cms.db.Site;
import com.psddev.cms.db.ToolUi;
import com.psddev.cms.tool.PageWriter;
import com.psddev.cms.tool.Search;
import com.psddev.cms.tool.SearchResultRenderer;
import com.psddev.cms.tool.SearchResultSuggester;
import com.psddev.cms.tool.ToolPageContext;
import com.psddev.dari.db.Database;
import com.psddev.dari.db.ObjectField;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.PredicateParser;
import com.psddev.dari.db.SolrDatabase;
import com.psddev.dari.db.State;
import com.psddev.dari.util.ObjectUtils;
import com.psddev.dari.util.StringUtils;
public class SolrSearchResultSuggester implements SearchResultSuggester {
private static final Logger LOGGER = LoggerFactory.getLogger(SolrSearchResultSuggester.class);
@Override
public double getPriority(Search search) {
return ObjectUtils.getClassByName("org.apache.solr.client.solrj.SolrQuery") != null ? DEFAULT_PRIORITY_LEVEL : -1;
}
@Override
public void writeHtml(Search search, ToolPageContext page) throws IOException {
String objectString = page.param(String.class, "object");
if (StringUtils.isBlank(objectString)) {
return;
}
Map<String, Object> objectData = (Map<String, Object>) ObjectUtils.fromJson(objectString);
if (ObjectUtils.isBlank(objectData)) {
return;
}
Object object = Database.Static.getDefault().getEnvironment().createObject(ObjectUtils.to(UUID.class, objectData.get("_type")), ObjectUtils.to(UUID.class, objectData.get("_id")));
State objectState = State.getInstance(object);
objectState.setValues(objectData);
ObjectType objectType = objectState.getType();
if (objectType == null) {
return;
}
String fieldName = page.param(String.class, "field");
ObjectField field = objectType.getField(fieldName);
if (field == null) {
return;
}
Map<Object, Float> suggestions = new HashMap<Object, Float>();
StringBuilder filter = new StringBuilder();
for (ObjectType t : field.as(ToolUi.class).findDisplayTypes()) {
filter.append(SolrDatabase.Static.escapeValue(t.getId()));
filter.append(" || ");
}
Site site = page.getUser().getCurrentSite();
for (Object item : findSimilar(object, filter, 10)) {
Float score = SolrDatabase.Static.getNormalizedScore(item);
if (score != null && score > 0.7) {
if (site != null
&& !PredicateParser.Static.evaluate(item, site.itemsPredicate())) {
continue;
}
suggestions.put(item, score);
}
}
filter.setLength(0);
String fieldClass = field.getJavaDeclaringClassName();
for (ObjectType type : objectState.getDatabase().getEnvironment().getTypes()) {
ObjectField f = type.getField(fieldName);
if (f != null && ObjectUtils.equals(fieldClass, f.getJavaDeclaringClassName())) {
filter.append(SolrDatabase.Static.escapeValue(type.getId()));
filter.append(" || ");
}
}
Map<Object, Integer> similar = new HashMap<Object, Integer>();
for (Object item : findSimilar(object, filter, 20)) {
Float score = SolrDatabase.Static.getNormalizedScore(item);
if (score > 0.7) {
if (site != null
&& !PredicateParser.Static.evaluate(item, site.itemsPredicate())) {
continue;
}
Object value = State.getInstance(item).get(fieldName);
if (value == null) {
continue;
}
if (value instanceof Map) {
value = ((Map<?, ?>) value).values();
}
if (value instanceof Iterable) {
for (Object v : (Iterable<?>) value) {
incrementCount(similar, v);
}
} else {
incrementCount(similar, value);
}
}
}
for (Map.Entry<Object, Integer> entry : similar.entrySet()) {
Integer count = entry.getValue();
if (count > 1) {
suggestions.put(entry.getKey(), ((float) count) / similar.size());
}
}
if (suggestions.isEmpty()) {
return;
}
Object fieldValue = State.getInstance(object).get(fieldName);
if (fieldValue != null) {
if (fieldValue instanceof Map) {
fieldValue = ((Map<?, ?>) fieldValue).values();
}
if (fieldValue instanceof Iterable) {
for (Object v : (Iterable<?>) fieldValue) {
suggestions.remove(v);
}
} else {
suggestions.remove(fieldValue);
}
}
if (suggestions.isEmpty()) {
return;
}
List<Map.Entry<Object, Float>> suggestionsList = new ArrayList<Map.Entry<Object, Float>>(suggestions.entrySet());
Collections.sort(suggestionsList, new Comparator<Map.Entry<Object, Float>>() {
public int compare(Map.Entry<Object, Float> x, Map.Entry<Object, Float> y) {
float xv = x.getValue();
float yv = y.getValue();
return xv == yv ? 0 : xv < yv ? 1 : -1;
}
});
List<Object> sortedSuggestions = new ArrayList<Object>();
for (Map.Entry<Object, Float> entry : suggestionsList) {
sortedSuggestions.add(entry.getKey());
}
if (sortedSuggestions.size() > 10) {
sortedSuggestions = sortedSuggestions.subList(0, 10);
}
PageWriter writer = page.getWriter();
writer.start("div", "class", "searchSuggestions");
writer.start("h2");
writer.html(page.localize(SolrSearchResultSuggester.class, "subtitle.suggestions"));
writer.end();
new SearchResultRenderer(page, search).renderList(sortedSuggestions);
writer.end();
}
private static List<?> findSimilar(Object object, StringBuilder filter, int rows) {
if (filter.length() < 1) {
return Collections.emptyList();
}
filter.setLength(filter.length() - 4);
filter.insert(0, "typeId:(");
filter.append(")");
List<Object> items = new ArrayList<>();
SolrDatabase solr = Database.Static.getFirst(SolrDatabase.class);
try {
SolrQuery solrQuery = solr.buildSimilarQuery(object);
solrQuery.add(CommonParams.FQ, filter.toString());
solrQuery.setStart(0);
solrQuery.setRows(rows);
items = solr.queryPartialWithOptions(solrQuery, null).getItems();
} catch (Throwable error) {
LOGGER.debug("Solrj (an optional dependency) was not found.", error);
}
return items;
}
private static void incrementCount(Map<Object, Integer> map, Object object) {
Integer count = map.get(object);
map.put(object, count != null ? count + 1 : 1);
}
}