/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* bdelbosc
*/
package org.nuxeo.elasticsearch.fetcher;
import static org.nuxeo.elasticsearch.ElasticSearchConstants.HIGHLIGHT_CTX_DATA;
import java.io.Serializable;
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 org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.highlight.HighlightField;
import org.nuxeo.ecm.core.api.CoreInstance;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
import org.nuxeo.ecm.core.query.sql.NXQL;
/**
* @since 6.0
*/
public class VcsFetcher extends Fetcher {
private static final int CHUNK_SIZE = 100;
public VcsFetcher(CoreSession session, SearchResponse response, Map<String, String> repoNames) {
super(session, response, repoNames);
}
@Override
public DocumentModelListImpl fetchDocuments() {
Map<String, List<String>> repoHits = getHitsPerRepository();
List<DocumentModel> docs = new ArrayList<>();
String openSessionRepository = getSession().getRepositoryName();
boolean closeSession;
CoreSession session;
for (String repo : repoHits.keySet()) {
if (openSessionRepository.equals(repo)) {
session = getSession();
closeSession = false;
} else {
session = CoreInstance.openCoreSession(repo);
closeSession = true;
}
try {
docs.addAll(fetchFromVcs(repoHits.get(repo), session));
} finally {
if (closeSession) {
session.close();
}
}
}
sortResults(docs);
addHighlights(docs);
DocumentModelListImpl ret = new DocumentModelListImpl(docs.size());
if (!docs.isEmpty()) {
ret.addAll(docs);
}
return ret;
}
private Map<String, List<String>> getHitsPerRepository() {
Map<String, List<String>> ret = new HashMap<>();
for (SearchHit hit : getResponse().getHits()) {
String repoName = getRepoForIndex(hit.getIndex());
List<String> docIds = ret.get(repoName);
if (docIds == null) {
docIds = new ArrayList<>();
ret.put(repoName, docIds);
}
docIds.add(hit.getId());
}
return ret;
}
private List<DocumentModel> fetchFromVcs(List<String> ids, CoreSession session) {
List<DocumentModel> ret = null;
int size = ids.size();
int start = 0;
int end = Math.min(CHUNK_SIZE, size);
boolean done = false;
while (!done) {
List<DocumentModel> docs = fetchFromVcsChunk(ids.subList(start, end), session);
if (ret == null) {
ret = docs;
} else {
ret.addAll(docs);
}
if (end >= ids.size()) {
done = true;
} else {
start = end;
end = Math.min(start + CHUNK_SIZE, size);
}
}
return ret;
}
private List<DocumentModel> fetchFromVcsChunk(final List<String> ids, CoreSession session)
{
StringBuilder sb = new StringBuilder();
sb.append("SELECT * FROM Document, Relation WHERE ecm:uuid IN (");
for (int i = 0; i < ids.size(); i++) {
sb.append(NXQL.escapeString(ids.get(i)));
if (i < ids.size() - 1) {
sb.append(", ");
}
}
sb.append(")");
return session.query(sb.toString());
}
private void addHighlights(List<DocumentModel> docs) {
for (SearchHit hit : getResponse().getHits()) {
for (DocumentModel doc : docs) {
String docId = doc.getRepositoryName() + doc.getId();
String hitId = getRepoForIndex(hit.getIndex()) + hit.getId();
if (docId.equals(hitId)) {
// Add highlight if it exists
Map<String, HighlightField> esHighlights = hit.highlightFields();
if (!esHighlights.isEmpty()) {
Map<String, List<String>> fields = new HashMap<>();
for (Map.Entry<String, HighlightField> entry : esHighlights.entrySet()) {
String field = entry.getKey();
List<String> list = new ArrayList<>();
for (Text fragment : entry.getValue().getFragments()) {
list.add(fragment.toString());
}
fields.put(field, list);
}
doc.putContextData(HIGHLIGHT_CTX_DATA, (Serializable) fields);
}
break;
}
}
}
}
private void sortResults(List<DocumentModel> docs) {
final List<String> ids = new ArrayList<>();
for (SearchHit hit : getResponse().getHits()) {
ids.add(getRepoForIndex(hit.getIndex()) + hit.getId());
}
Collections.sort(docs, new Comparator<DocumentModel>() {
@Override
public int compare(DocumentModel a, DocumentModel b) {
return ids.indexOf(a.getRepositoryName() + a.getId()) - ids.indexOf(b.getRepositoryName() + b.getId());
}
});
}
}