Commit 1b11596b authored by Matija Obreza's avatar Matija Obreza
Browse files

Explorer: Autocompleters on country, holding institute, list of coded

values
parent 63e71915
......@@ -46,6 +46,10 @@ public class FaoInstituteBridge implements FieldBridge {
LOG.debug("IDX." + name + " = " + sd.toString());
Field field = new Field(name, sd.toString(), luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector());
field.setBoost(luceneOptions.getBoost());
if (institute.getAccessionCount() > 0)
field.setBoost(2 * luceneOptions.getBoost());
document.add(field);
}
......
......@@ -56,7 +56,6 @@ public class Country extends BusinessModel {
private boolean current;
@Column(unique = false, nullable = false, length = 200)
@Field(name = "body", store = Store.NO)
private String name;
@Column(length = 3)
......@@ -67,6 +66,7 @@ public class Country extends BusinessModel {
* Localized names
*/
@Lob
@Field(name = "body", store = Store.NO)
private String nameL;
@Lob
......
......@@ -34,6 +34,7 @@ import org.genesys2.server.model.AclAwareModel;
import org.genesys2.server.model.BusinessModel;
import org.genesys2.server.model.EntityId;
import org.hibernate.annotations.Index;
import org.hibernate.search.annotations.Boost;
import org.hibernate.search.annotations.ClassBridge;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
......@@ -43,7 +44,7 @@ import org.hibernate.search.annotations.Store;
@Table(name = "faoinstitute", uniqueConstraints = @UniqueConstraint(columnNames = { "code" }))
@org.hibernate.annotations.Table(appliesTo = "faoinstitute", indexes = { @Index(columnNames = { "code" }, name = "code_FAOINSTITUTE") })
@Indexed
@ClassBridge(name = "body", impl = FaoInstituteBridge.class)
@ClassBridge(name = "body", boost = @Boost(1.3f), impl = FaoInstituteBridge.class)
public class FaoInstitute extends BusinessModel implements GeoReferencedEntity, AclAwareModel, EntityId {
/**
......
......@@ -20,6 +20,7 @@ import java.util.Collection;
import java.util.List;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
......@@ -40,11 +41,13 @@ public interface GenesysFilterService {
}
public enum FilterType {
EXACT, RANGE, LIST
EXACT, RANGE, LIST, AUTOCOMPLETE
}
}
List<GenesysFilter> selectFilters(String[] selectedFilters);
Collection<GenesysFilter> generateFilters(Collection<Long> methodIds);
List<LabelValue<String>> autocomplete(String filter, String ac);
}
......@@ -16,6 +16,8 @@
package org.genesys2.server.service;
import java.util.List;
import org.hibernate.search.SearchException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
......@@ -24,4 +26,6 @@ public interface SearchService {
Page<?> search(String searchQuery, String[] fields, Pageable pageable, Class<?> ... targetClass) throws SearchException;
<T> List<T> autocomplete(String searchQuery, Class<T> clazz);
}
......@@ -22,6 +22,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
......@@ -34,11 +35,16 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.genesys.Method;
import org.genesys2.server.model.genesys.TraitCode;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.persistence.domain.AccessionRepository;
import org.genesys2.server.persistence.domain.MethodRepository;
import org.genesys2.server.persistence.domain.TraitValueRepository;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysFilterService.GenesysFilter.DataType;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.SearchService;
import org.genesys2.server.service.TraitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
......@@ -78,6 +84,12 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
private JdbcTemplate jdbcTemplate;
@Autowired
private InstituteService instituteService;
@Autowired
private SearchService searchService;
@Autowired
public void setDataSource(final DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
......@@ -90,13 +102,13 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
this.availableFilters.add(new GenesysFilterImpl("genus", DataType.STRING));
this.availableFilters.add(new GenesysFilterImpl("taxon", DataType.STRING));
this.availableFilters.add(new GenesysFilterImpl("origin", DataType.FIXEDSTRING, 3));
this.availableFilters.add(new GenesysAutocompleteFilterImpl("origin", "/explore/ac/country"));
this.availableFilters.add(new GenesysFilterImpl("lat", DataType.NUMERIC));
this.availableFilters.add(new GenesysFilterImpl("lon", DataType.NUMERIC));
this.availableFilters.add(new GenesysFilterImpl("elevation", DataType.NUMERIC));
this.availableFilters.add(new GenesysFilterImpl("organization", DataType.STRING));
this.availableFilters.add(new GenesysFilterImpl("institute", DataType.FIXEDSTRING, 6));
this.availableFilters.add(new GenesysAutocompleteFilterImpl("institute", "/explore/ac/instCode"));
this.availableFilters.add(new GenesysFilterImpl("accenumb", DataType.NUMERIC));
this.availableFilters.add(new GenesysFilterImpl("inSvalbard", DataType.BOOLEAN));
this.availableFilters.add(new GenesysFilterImpl("mls", DataType.BOOLEAN));
......@@ -146,12 +158,37 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
if (method == null) {
continue;
}
filters.add(new GenesysMethodFilterImpl("gm:" + method.getId(), method.getParameter().getTitle(), method.getFieldType() == 1
|| method.getFieldType() == 2 ? DataType.NUMERIC : DataType.STRING));
filters.add(toFilter(method));
}
return filters;
}
private GenesysFilter toFilter(Method method) {
DataType dataType = null;
switch (method.getFieldType()) {
case 1:
case 2:
dataType = DataType.NUMERIC;
break;
case 0:
dataType = DataType.STRING;
}
GenesysFilter filter = null;
if (method.isCoded()) {
Map<String, Long> stats = traitService.getMethodStatistics(method);
List<ValueName<?>> options = new ArrayList<ValueName<?>>();
for (TraitCode traitCode : TraitCode.parseOptions(method.getOptions())) {
options.add(new ValueName<String>(traitCode.getCode(), traitCode.getValue(), stats.get(traitCode.getCode())));
}
filter = new GenesysCodedMethodFilterImpl("gm:" + method.getId(), method.getParameter().getTitle(), dataType, options);
} else {
filter = new GenesysMethodFilterImpl("gm:" + method.getId(), method.getParameter().getTitle(), dataType);
}
return filter;
}
@Override
public Page<Accession> listAccessions(JsonNode jsonTree, Pageable pageable) {
Iterator<Entry<String, JsonNode>> fields = jsonTree.fields();
......@@ -425,13 +462,33 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
params.add(value.asDouble());
}
/**
* Filtering autocompleter
*/
@Override
public List<LabelValue<String>> autocomplete(String filter, String ac) {
List<LabelValue<String>> completed = new ArrayList<LabelValue<String>>();
if ("instCode".equalsIgnoreCase(filter)) {
List<FaoInstitute> faoInst = searchService.autocomplete(ac + "*", FaoInstitute.class);
for (FaoInstitute inst : faoInst) {
completed.add(new LabelValue<String>(inst.getCode(), inst.getCode() + ", " + inst.getFullName()));
}
} else if ("country".equalsIgnoreCase(filter)) {
List<Country> countries = searchService.autocomplete(ac + "*", Country.class);
for (Country c : countries) {
completed.add(new LabelValue<String>(c.getCode3(), c.getCode3() + ", " + c.getName()));
}
}
return completed;
}
public static class GenesysFilterImpl implements GenesysFilter {
private String name;
private DataType dataType;
private FilterType filterType;
private Integer maxLength;
public boolean isCore() {
return true;
}
......@@ -446,6 +503,13 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
this.maxLength = null;
}
public GenesysFilterImpl(String name, DataType type, FilterType filterType) {
this.name = name;
this.dataType = type;
this.filterType = filterType;
this.maxLength = null;
}
public GenesysFilterImpl(String name, DataType type, int i) {
this.name = name;
this.dataType = type;
......@@ -478,9 +542,56 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
}
}
public static class ValueName<T> {
private T value;
private String name;
private Long count;
public ValueName(T value, String name) {
this.value = value;
this.name = name;
}
public ValueName(T value, String name, Long count) {
this.value = value;
this.name = name;
this.count = count;
}
public T getValue() {
return value;
}
public String getName() {
return name;
}
public Long getCount() {
return count;
}
}
public static class LabelValue<T> {
private T value;
private String label;
public LabelValue(T value, String label) {
this.value = value;
this.label = label;
}
public T getValue() {
return value;
}
public String getLabel() {
return label;
}
}
public static class GenesysMethodFilterImpl extends GenesysFilterImpl {
private String title;
@Override
public boolean isCore() {
return false;
......@@ -491,8 +602,39 @@ public class GenesysFilterServiceImpl implements GenesysFilterService {
this.title = title;
}
public GenesysMethodFilterImpl(String name, String title, DataType dataType, FilterType filterType) {
super(name, dataType, filterType);
this.title = title;
}
public String getTitle() {
return title;
}
}
public static class GenesysCodedMethodFilterImpl extends GenesysMethodFilterImpl {
private List<ValueName<?>> options;
public GenesysCodedMethodFilterImpl(String name, String title, DataType dataType, List<ValueName<?>> options) {
super(name, title, dataType, FilterType.LIST);
this.options = Collections.unmodifiableList(options);
}
public List<ValueName<?>> getOptions() {
return options;
}
}
public static class GenesysAutocompleteFilterImpl extends GenesysFilterImpl {
private String autocompleteUrl;
public GenesysAutocompleteFilterImpl(String name, String autocompleteUrl) {
super(name, DataType.STRING, FilterType.AUTOCOMPLETE);
this.autocompleteUrl = autocompleteUrl;
}
public String getAutocompleteUrl() {
return autocompleteUrl;
}
}
}
......@@ -21,6 +21,7 @@ import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
......@@ -47,12 +48,12 @@ public class SearchServiceImpl implements SearchService {
private EntityManager entityManager;
/**
* @param classes
* @param classes
* @return
* @throws SearchException
*/
@Override
public Page<?> search(String searchQuery, String[] fields, Pageable pageable, Class<?> ... classes) throws SearchException {
public Page<?> search(String searchQuery, String[] fields, Pageable pageable, Class<?>... classes) throws SearchException {
LOG.debug("Searching for: " + searchQuery);
LucenePage<Object> lucenePage = new LucenePage<Object>(pageable.getPageNumber(), pageable.getPageSize());
......@@ -70,8 +71,7 @@ public class SearchServiceImpl implements SearchService {
org.hibernate.search.jpa.FullTextQuery query = ftEm.createFullTextQuery(luceneQuery, classes);
lucenePage.setTotalElements(query.getResultSize());
@SuppressWarnings("unchecked")
List<Object> data = query.setMaxResults(pageable.getPageSize()).setFirstResult(pageable.getPageNumber() * pageable.getPageSize())
.getResultList();
List<Object> data = query.setMaxResults(pageable.getPageSize()).setFirstResult(pageable.getPageNumber() * pageable.getPageSize()).getResultList();
prefetch(data);
......@@ -95,6 +95,27 @@ public class SearchServiceImpl implements SearchService {
}
}
@SuppressWarnings("unchecked")
@Override
public <T> List<T> autocomplete(String searchQuery, Class<T> clazz) {
if (StringUtils.isBlank(searchQuery) || searchQuery.length() < 3)
return null;
FullTextEntityManager ftEm = Search.getFullTextEntityManager(this.entityManager);
org.apache.lucene.search.Query luceneQuery = null;
try {
luceneQuery = getLuceneQuery(searchQuery, new String[] { "body" });
org.hibernate.search.jpa.FullTextQuery query = ftEm.createFullTextQuery(luceneQuery, clazz);
return (List<T>) query.setMaxResults(10).setFirstResult(0).getResultList();
} catch (ParseException | NullPointerException e) {
throw new SearchException(e);
}
}
/**
* @param searchQuery
* @return
......
......@@ -30,12 +30,16 @@ import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.TraitService;
import org.genesys2.server.service.impl.GenesysFilterServiceImpl.LabelValue;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
......@@ -112,7 +116,7 @@ public class ExplorerController extends BaseController {
} catch (JsonProcessingException e) {
_logger.error(e.getMessage(), e);
}
model.addAttribute("filter", jsonFilter);
return "redirect:/explore/pick";
......@@ -123,7 +127,7 @@ public class ExplorerController extends BaseController {
@RequestParam(value = "pick", required = false) String[] pick, @RequestParam(value = "crop", required = false) String shortName) {
doPickFilters(model, jsonFilter, pick, shortName);
return "redirect:/explore/filter";
}
......@@ -163,4 +167,10 @@ public class ExplorerController extends BaseController {
return "/filter/filter";
}
@RequestMapping(value = "/ac/{field}", produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public List<LabelValue<String>> autocomplete(@PathVariable("field") String filter, @RequestParam(value = "term", required = false) String ac) {
return filterService.autocomplete(filter, ac);
}
}
......@@ -69,12 +69,12 @@
<form method="post" action="<c:url value="/admin/reindexEntity" />">
<select name="entity">
<option value="org.genesys2.rest.common.model.impl.Country">Countries</option>
<option value="org.genesys2.rest.common.model.impl.FaoInstitute">WIEWS Institutes</option>
<option value="org.genesys2.rest.common.model.impl.ActivityPost">Posts</option>
<option value="org.genesys2.rest.common.model.impl.Article">Articles</option>
<option value="org.genesys2.rest.common.model.impl.Organization">Organizations</option>
<option value="org.genesys2.rest.common.model.genesys.Accession">Accessions</option>
<option value="org.genesys2.server.model.impl.Country">Countries</option>
<option value="org.genesys2.server.model.impl.FaoInstitute">WIEWS Institutes</option>
<option value="org.genesys2.server.model.impl.ActivityPost">Posts</option>
<option value="org.genesys2.server.model.impl.Article">Articles</option>
<option value="org.genesys2.server.model.impl.Organization">Organizations</option>
<option value="org.genesys2.server.model.genesys.Accession">Accessions</option>
</select> <input type="submit" class="btn btn-default" value="Reindex search indexes" />
</form>
......
......@@ -34,11 +34,27 @@
</div>
<div class="col-lg-5 filter-new">
<c:choose>
<c:when test="${filter.filterType=='LIST'}">
<div class="">
<c:forEach items="${filter.options}" var="option">
<div><label><input type="checkbox" value="${option.value}" /> <c:out value="${option.name}" /></label> <span class="pull-right"><c:out value="${option.count}" /></span></div>
</c:forEach>
</div>
</c:when>
<c:when test="${filter.filterType=='AUTOCOMPLETE'}">
<div class="ui-front">
<div class="form-group input-group"><span class="input-group-btn"><input class="span2 form-control autocomplete-filter" x-source="${filter.autocompleteUrl}" type="text" /><button class="btn notimportant">+</button></span></div>
</div>
</c:when>
<c:when test="${filter.dataType=='NUMERIC'}">
<div class="form-group input-group"><span class="input-group-btn"><input class="span1 form-control" type="text" /><input class="span1 form-control" type="text" /><button class="btn notimportant">+</button></span></div>
</c:when>
<c:when test="${filter.dataType=='BOOLEAN'}">
<div class=""><select><option value="true"><spring:message code="boolean.true" /></option><option value="false"><spring:message code="boolean.false" /></option><option value="null"><spring:message code="boolean.null" /></option></select><button class="notimportant">+</button></div>
<div class="">
<div><label><input type="checkbox" value="true"> <spring:message code="boolean.true" /></label></div>
<div><label><input type="checkbox" value="false"> <spring:message code="boolean.false" /></label></div>
<div><label><input type="checkbox" value="null"> <spring:message code="boolean.null" /></label></div>
</div>
</c:when>
<c:otherwise>
<div class="form-group input-group"><span class="input-group-btn"><input class="span2 form-control" type="text" /><button class="btn notimportant">+</button></span></div>
......@@ -66,6 +82,7 @@
<content tag="javascript">
<script type="text/javascript" src="<c:url value="/html/js/jquery-ui.min.js" />"></script>
<script type="text/javascript">
jQuery(document).ready(function() {
// alert('${jsonString}');
......@@ -154,8 +171,8 @@ jQuery(document).ready(function() {
} else {
filters[name][filters[name].length]=r;
}
if (inputs[0]) { inputs[0].value=''; inputs[0].focus(); }
if (inputs[1]) inputs[1].value='';
if (inputs[0] && inputs[0].type=="text") { inputs[0].value=''; inputs[0].focus(); }
if (inputs[1] && inputs[1].type=="text") inputs[1].value='';
}
console.log(filters);
filters=FF.cleanup(filters);
......@@ -189,10 +206,12 @@ jQuery(document).ready(function() {
var name=$(this).closest(".clearfix").attr("x-filtername");
//debugger;
console.log("Delete! " + idx + " from " + name);
filters[name].splice(idx, 1);
if (filters[name].length==0)
delete filters[name];
console.log(filters[name]);
if (filters[name]) {
filters[name].splice(idx, 1);
if (filters[name].length==0)
delete filters[name];
console.log(filters[name]);
}
$(this).remove();
FF.refreshJson(filters);
});
......@@ -201,6 +220,24 @@ jQuery(document).ready(function() {
event.preventDefault();
FF.addFilterValue(this);
});
$("body").on("click", ".filter-new input[type=checkbox]", function(event) {
// event.preventDefault();
if (this.checked) {
FF.addFilterValue(this);
} else {
var name=$(this).closest(".clearfix").attr("x-filtername");
//debugger;
console.log("Delete! " + this.value + " from " + name);
if (filters[name]) {
filters[name].splice(filters[name].indexOf(this.value), 1);
if (filters[name].length==0)
delete filters[name];
}
FF.refreshJson(filters);
FF.updateValues("#filter-" + name.replace(":", "_") + ".filter-values", name, filters[name]);
}
});
$("body").on("keydown", ".filter-new input", function(event) {
if (event.keyCode==13) {
......@@ -209,6 +246,11 @@ jQuery(document).ready(function() {
}
});
$(".autocomplete-filter").each(function() {
var t=$(this);
t.autocomplete({ delay: 200, minLength: 3, source: t.attr('x-source'), messages: { noResults: '', results: function() {} } });
});
FF.refreshJson(filters);
});
</script>
......
......@@ -31,7 +31,7 @@
<c:forEach items="${availableFilters}" var="filter">
<div class="row filter-block">
<div class="col-lg-offset-3 col-lg-9">
<div><label><input type="checkbox" style="margin-right: 1em;" name="pick" <c:if test="${selectedFilters.contains(filter.key)}">checked="checked"</c:if> value="${filter.key}" /><span dir="ltr"><spring:message code="filter.${filter.key}" /></span></label></div>
<div><label><input type="checkbox" name="pick" <c:if test="${selectedFilters.contains(filter.key)}">checked="checked"</c:if> value="${filter.key}" /><span dir="ltr"><spring:message code="filter.${filter.key}" /></span></label></div>
</div>
</div>
</c:forEach>
......
......@@ -1274,6 +1274,10 @@ table.crop-details h4 {
padding:10px;
}
.filter-block input[type=checkbox] {
margin-right: 1em;
}
#allfilters .filter-block:nth-of-type(even) {
background:#edece7;
}
......@@ -1820,3 +1824,7 @@ html[dir="rtl"] ul.statistics .stats-number {
background-color: #FFF;
margin-bottom: 1em;
}
.ui-autocomplete.ui-widget {
font-size: 12px;
}