package controllers;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFactory;
import javax.jcr.query.InvalidQueryException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import org.apache.commons.lang.StringUtils;
import org.jcrom.Jcrom;
import play.libs.F;
import play.libs.Json;
import play.mvc.Result;
import play.mvc.With;
import providers.CacheableUserProvider;
import service.JcrSessionFactory;
import service.filestore.FileStore;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
@With(UncacheableAction.class)
public class Search extends SessionAwareController {
private static final String CONTENT = "content:";
private static final String FILE = "file:";
public static class SearchResult {
private final String id;
private final double score;
private String excerpt;
private final String type;
public SearchResult(String id, double score, String type) {
this.id = id;
this.score = score;
this.type = type;
}
public SearchResult(String id, double score, String excerpt, String type) {
this(id, score, type);
this.excerpt = excerpt;
}
public String getId() {
return id;
}
public double getScore() {
return score;
}
public String getExcerpt() {
return excerpt;
}
public void setExcerpt(String excerpt) {
this.excerpt = excerpt;
}
public String getType() {
return type;
}
@Override
public boolean equals(Object other) {
if (other instanceof SearchResult) {
return getId().equals(((SearchResult) other).getId());
}
return false;
}
@Override
public int hashCode() {
return getId().hashCode();
}
}
private final FileStore fileStore;
@Inject
public Search(final JcrSessionFactory sessionFactory, final Jcrom jcrom,
final CacheableUserProvider sessionHandler, final FileStore fileStore) {
super(sessionFactory, jcrom, sessionHandler);
this.fileStore = fileStore;
}
@SubjectPresent
public Result search(final String q) {
if (q.isEmpty()) {
return badRequest("Query string cannot be blank.");
} else {
return ok(Json.toJson(srch(q))).as("application/json; charset=utf-8");
}
}
private List<SearchResult> srch(final String q) {
return inUserSession(new F.Function<Session, List<SearchResult>>() {
@Override
public final List<SearchResult> apply(Session session) throws Exception {
Set<SearchResult> sresults = Sets.newHashSet();
if(StringUtils.startsWith(q, CONTENT)) {
sresults.addAll(searchContent(session, StringUtils.substring(q, CONTENT.length())));
} else if(StringUtils.startsWith(q, FILE)) {
sresults.addAll(searchFilename(session, StringUtils.substring(q, FILE.length())));
} else {
sresults.addAll(searchContent(session, q));
sresults.addAll(searchFilename(session, q));
}
List<SearchResult> l = Lists.newArrayList(sresults);
Collections.sort(l, Collections.reverseOrder(new Comparator<SearchResult>() {
@Override
public int compare(SearchResult s0, SearchResult s1) {
return Double.compare(s0.getScore(), s1.getScore());
}}));
return l;
}
private Set<SearchResult> searchContent(Session session, String q) throws Exception {
if(StringUtils.isBlank(q)) {
return Collections.emptySet();
}
Map<String, SearchResult> srMap = Maps.newHashMap();
Set<SearchResult> result = Sets.newHashSet();
ValueFactory vf = session.getValueFactory();
QueryManager queryManager = session.getWorkspace().getQueryManager();
// How do we get the excerpt with JCR_SQL2?
Query query = queryManager.createQuery(
"SELECT * FROM [nt:resource] as s WHERE contains(s.*,$query) AND" +
" ISDESCENDANTNODE(s, '/filestore')",
javax.jcr.query.Query.JCR_SQL2);
query.bindValue("query", vf.createValue(q));
QueryResult qr = query.execute();
RowIterator iter = qr.getRows();
while (iter.hasNext()) {
Row row = iter.nextRow();
Node n = row.getNode().getParent().getParent().getParent();
SearchResult sr = new SearchResult(n.getIdentifier(), row.getScore(),
"content");
result.add(sr);
srMap.put(row.getNode().getIdentifier(), sr);
}
iter = fulltextQuery(queryManager, q);
while (iter.hasNext()) {
Row row = iter.nextRow();
SearchResult sr = srMap.get(row.getNode().getIdentifier());
if (sr != null) {
sr.setExcerpt(row.getValue("rep:excerpt(.)").getString());
}
}
return result;
}
@SuppressWarnings("deprecation")
protected RowIterator fulltextQuery(QueryManager queryManager, String q)
throws RepositoryException, InvalidQueryException {
// TODO figure out how to do bindValue with javax.jcr.query.Query.SQL
return queryManager
.createQuery(
"SELECT * FROM nt:resource WHERE contains(.,'" + q + "')",
javax.jcr.query.Query.SQL).execute().getRows();
}
private Set<SearchResult> searchFilename(Session session, String q) throws Exception {
if(StringUtils.isBlank(q)) {
return Collections.emptySet();
}
Set<SearchResult> result = Sets.newHashSet();
FileStore.Manager fm = fileStore.getManager(session);
QueryManager queryManager = session.getWorkspace().getQueryManager();
Query query = queryManager.createQuery(
"SELECT * FROM [nt:unstructured] as file " +
"WHERE LOWER(localname()) LIKE $query AND ISDESCENDANTNODE(file, '/filestore')",
javax.jcr.query.Query.JCR_SQL2);
ValueFactory vf = session.getValueFactory();
query.bindValue("query", vf.createValue("%" + StringUtils.lowerCase(q) + "%"));
QueryResult qr = query.execute();
RowIterator iter = qr.getRows();
while (iter.hasNext()) {
Row row = iter.nextRow();
Node n = row.getNode();
FileStore.FileOrFolder fof = fm.getByIdentifier(n.getIdentifier());
if(fof!= null) {
SearchResult sr = new SearchResult(n.getIdentifier(), row.getScore(),
highlight(fof, q), "filename");
result.add(sr);
}
}
return result;
}
private String highlight(FileStore.FileOrFolder fof, String query) {
int start = StringUtils.indexOfIgnoreCase(fof.getName(), query);
if(start == -1) {
return fof.getPath();
}
return StringUtils.join(new String[] {
StringUtils.substring(fof.getName(), 0, start),
"<strong>",
StringUtils.substring(fof.getName(), start, start + query.length()),
"</strong>",
StringUtils.substring(fof.getName(), start + query.length())});
}
});
}
}