Commit c38258c5 authored by Matija Obreza's avatar Matija Obreza

Added score and score-based search, added <mini:* tags for display, returning entities from search

parent 5e6aa0f7
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package org.genesys2.server.model.elastic; package org.genesys2.server.model.elastic;
import java.util.Date; import java.util.Date;
import java.util.Locale;
import org.genesys2.server.model.impl.ClassPK; import org.genesys2.server.model.impl.ClassPK;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
...@@ -25,18 +24,11 @@ import org.springframework.data.elasticsearch.annotations.Field; ...@@ -25,18 +24,11 @@ import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldIndex; import org.springframework.data.elasticsearch.annotations.FieldIndex;
import org.springframework.data.elasticsearch.annotations.FieldType; import org.springframework.data.elasticsearch.annotations.FieldType;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.eclipsesource.json.ParseException;
/** /**
* <code>FullTextDocument</code> is used in Elasticsearch mapping * <code>FullTextDocument</code> is used in Elasticsearch mapping
*/ */
@Document(indexName = "fulltext", refreshInterval = "60s") @Document(indexName = "fulltext", refreshInterval = "60s")
public class FullTextDocument { public class FullTextDocument {
// FIXME Revisit!
private static final String ENGLISH_LOCALE = "en";
@Id @Id
private Long id; private Long id;
...@@ -61,6 +53,10 @@ public class FullTextDocument { ...@@ -61,6 +53,10 @@ public class FullTextDocument {
@Field(index = FieldIndex.not_analyzed, type = FieldType.Date) @Field(index = FieldIndex.not_analyzed, type = FieldType.Date)
private Date lastModifiedDate; private Date lastModifiedDate;
/** The score here will boost result relevance */
@Field(index = FieldIndex.not_analyzed)
private float score = 0.5f;
public Long getId() { public Long getId() {
return this.id; return this.id;
} }
...@@ -101,21 +97,6 @@ public class FullTextDocument { ...@@ -101,21 +97,6 @@ public class FullTextDocument {
this.summary = summary; this.summary = summary;
} }
public String getTextByLocale(final Locale locale) {
try {
final JsonObject json = JsonObject.readFrom(this.body);
JsonValue value = json.get(locale.getLanguage());
if (value == null) {
value = json.get(ENGLISH_LOCALE);
}
return value.toString().replace("\"", "");
} catch (final ParseException ignored) {
}
return getBody();
}
public String getLanguage() { public String getLanguage() {
return this.language; return this.language;
} }
...@@ -139,4 +120,12 @@ public class FullTextDocument { ...@@ -139,4 +120,12 @@ public class FullTextDocument {
public void setLastModifiedDate(final Date lastModifiedDate) { public void setLastModifiedDate(final Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate; this.lastModifiedDate = lastModifiedDate;
} }
public float getScore() {
return score;
}
public void setScore(float score) {
this.score = score;
}
} }
...@@ -40,6 +40,8 @@ public interface ContentService { ...@@ -40,6 +40,8 @@ public interface ContentService {
ClassPK getClassPk(String shortName); ClassPK getClassPk(String shortName);
ClassPK getClassPk(Class<?> clazz);
/** /**
* Load article with {@link ClassPK} of clazz with specified id and the * Load article with {@link ClassPK} of clazz with specified id and the
* "slug" for the locale * "slug" for the locale
......
...@@ -43,4 +43,6 @@ public interface FullTextSearchService { ...@@ -43,4 +43,6 @@ public interface FullTextSearchService {
void reindex(String type); void reindex(String type);
void deleteIndex(String indexName); void deleteIndex(String indexName);
Page<?> search(String query, Pageable pageable, Class<?> type) throws SearchException;
} }
...@@ -247,6 +247,7 @@ public class ContentServiceImpl implements ContentService { ...@@ -247,6 +247,7 @@ public class ContentServiceImpl implements ContentService {
return article; return article;
} }
@Override
public ClassPK getClassPk(Class<?> clazz) { public ClassPK getClassPk(Class<?> clazz) {
return classPkRepository.findByClassName(clazz.getName()); return classPkRepository.findByClassName(clazz.getName());
} }
......
...@@ -16,8 +16,10 @@ ...@@ -16,8 +16,10 @@
package org.genesys2.server.service.impl; package org.genesys2.server.service.impl;
import static org.elasticsearch.index.query.QueryBuilders.boolQuery; import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.index.query.QueryBuilders.functionScoreQuery;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
import static org.elasticsearch.index.query.QueryBuilders.queryString; import static org.elasticsearch.index.query.QueryBuilders.queryString;
import static org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders.fieldValueFactorFunction;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
...@@ -40,10 +42,10 @@ import org.elasticsearch.cluster.metadata.AliasMetaData; ...@@ -40,10 +42,10 @@ import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap; import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.genesys2.server.model.BusinessModel; import org.genesys2.server.model.BusinessModel;
import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.elastic.FullTextDocument; import org.genesys2.server.model.elastic.FullTextDocument;
import org.genesys2.server.model.impl.ActivityPost; import org.genesys2.server.model.impl.ActivityPost;
import org.genesys2.server.model.impl.Article; import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.ClassPK;
import org.genesys2.server.model.impl.Country; import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.FaoInstitute; import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.persistence.domain.ActivityPostRepository; import org.genesys2.server.persistence.domain.ActivityPostRepository;
...@@ -59,9 +61,11 @@ import org.springframework.beans.factory.InitializingBean; ...@@ -59,9 +61,11 @@ import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.FacetedPage;
import org.springframework.data.elasticsearch.core.query.AliasQuery; import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
...@@ -69,6 +73,7 @@ import org.springframework.data.elasticsearch.core.query.SearchQuery; ...@@ -69,6 +73,7 @@ import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.jdbc.core.RowCallbackHandler; import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.hazelcast.core.ILock; import com.hazelcast.core.ILock;
...@@ -82,7 +87,7 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial ...@@ -82,7 +87,7 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial
private static final String INDEXALIAS_FULLTEXT_READ = "fulltextRead"; private static final String INDEXALIAS_FULLTEXT_READ = "fulltextRead";
private static final String INDEXALIAS_FULLTEXT_WRITE = "fulltextWrite"; private static final String INDEXALIAS_FULLTEXT_WRITE = "fulltextWrite";
private static final String ACCESSION_SECTION = "Accession"; private static final String ACCESSION_SECTION = "accession";
private static final String CLASSPK_SHORTNAME = "classPK.shortName"; private static final String CLASSPK_SHORTNAME = "classPK.shortName";
private static final String REINDEX_TYPE_ALL = "All"; private static final String REINDEX_TYPE_ALL = "All";
...@@ -151,19 +156,68 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial ...@@ -151,19 +156,68 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial
} }
} }
@Override
@Transactional(readOnly = true)
public Page<?> search(final String query, final Pageable pageable, final Class<?> clazz) throws SearchException {
ClassPK classPK = contentService.getClassPk(clazz);
if (classPK == null) {
throw new SearchException("No such fulltext type");
}
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices(INDEXALIAS_FULLTEXT_READ)
.withQuery(
functionScoreQuery(
boolQuery().must(matchQuery(CLASSPK_SHORTNAME, classPK.getShortName())).must(
queryString(query).defaultOperator(QueryStringQueryBuilder.Operator.AND))).add(
fieldValueFactorFunction("score").factor(1.0f))).withPageable(pageable).build();
try {
FacetedPage<FullTextDocument> fulltextResults = this.elasticsearchTemplate.queryForPage(searchQuery, FullTextDocument.class);
return toEntities(fulltextResults, pageable);
} catch (final Throwable e) {
throw new SearchException(e.getMessage(), e);
}
}
private Page<?> toEntities(FacetedPage<FullTextDocument> fulltextResults, Pageable pageable) {
List content = new ArrayList(fulltextResults.getSize());
for (FullTextDocument document : fulltextResults.getContent()) {
content.add(toEntity(document.getClassPK().getClassName(), document.getId()));
}
return new PageImpl<>(content, pageable, fulltextResults.getTotalElements());
}
private Object toEntity(String className, Long id) {
if (FaoInstitute.class.getName().equals(className)) {
return instituteRepository.findOne(id);
} else if (Country.class.getName().equals(className)) {
return countryRepository.findOne(id);
} else {
throw new UnsupportedOperationException("Missing toEntity implementation for class=" + className);
}
}
@Override @Override
public Page<?> search(final String query, final Pageable pageable, final String section) throws SearchException { public Page<?> search(final String query, final Pageable pageable, final String section) throws SearchException {
ClassPK classPK = contentService.getClassPk(section);
if (classPK == null) {
throw new SearchException("No such fulltext type");
}
SearchQuery searchQuery; SearchQuery searchQuery;
Class clazz; Class clazz;
if (section.equals(ACCESSION_SECTION)) { if (section.equals(ACCESSION_SECTION)) {
searchQuery = new NativeSearchQueryBuilder().withIndices(ElasticsearchSearchServiceImpl.INDEXALIAS_PASSPORT_READ).withTypes(ElasticsearchSearchServiceImpl.PASSPORT_TYPE) throw new UnsupportedOperationException("Use ElasticsearchServiceImpl for accession search");
.withQuery(queryString(query).defaultOperator(QueryStringQueryBuilder.Operator.AND)).withPageable(pageable).build();
clazz = AccessionDetails.class;
} else { } else {
searchQuery = new NativeSearchQueryBuilder().withIndices(INDEXALIAS_FULLTEXT_READ).withQuery( searchQuery = new NativeSearchQueryBuilder()
boolQuery().must(queryString(query).defaultOperator(QueryStringQueryBuilder.Operator.AND)).must(matchQuery(CLASSPK_SHORTNAME, section))) .withIndices(INDEXALIAS_FULLTEXT_READ)
.withPageable(pageable).build(); .withQuery(
functionScoreQuery(
boolQuery().must(matchQuery(CLASSPK_SHORTNAME, section)).must(
queryString(query).defaultOperator(QueryStringQueryBuilder.Operator.AND))).add(
fieldValueFactorFunction("score").factor(1.0f))).withPageable(pageable).build();
clazz = FullTextDocument.class; clazz = FullTextDocument.class;
} }
...@@ -436,18 +490,29 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial ...@@ -436,18 +490,29 @@ public class FullTextSearchServiceImpl implements FullTextSearchService, Initial
} }
private void updateDocumentForCountry(Country country, FullTextDocument document) { private void updateDocumentForCountry(Country country, FullTextDocument document) {
document.setBody(country.getNameL());
document.setSummary(country.getCode3());
document.setUrlToContent("/geo/" + country.getCode3()); document.setUrlToContent("/geo/" + country.getCode3());
document.setLanguage(contentService.getDefaultLocale().getLanguage());
document.setSummary(country.getCode3() + " " + country.getNameL());
Article blurb = contentService.getArticle(country, "blurp", contentService.getDefaultLocale());
if (blurb != null) {
document.setBody(blurb.getSummary() + "\n\n" + blurb.getBody());
}
} }
private void updateDocumentForFaoInstitute(FaoInstitute institute, FullTextDocument document) { private void updateDocumentForFaoInstitute(FaoInstitute institute, FullTextDocument document) {
document.setUrlToContent("/wiews/" + institute.getCode());
document.setLanguage(contentService.getDefaultLocale().getLanguage());
document.setSummary(institute.getCode() + " " + institute.getAcronym() + " " + institute.getFullName()); document.setSummary(institute.getCode() + " " + institute.getAcronym() + " " + institute.getFullName());
Article blurb = contentService.getArticle(institute, "blurp", contentService.getDefaultLocale()); Article blurb = contentService.getArticle(institute, "blurp", contentService.getDefaultLocale());
if (blurb != null) { if (blurb != null) {
document.setBody(blurb.getSummary() + "\n\n" + blurb.getBody()); document.setBody(blurb.getSummary() + "\n\n" + blurb.getBody());
} }
document.setUrlToContent("/wiews/" + institute.getCode()); // If the institute has accessions, we boost it
if (institute.getAccessionCount() > 0) {
document.setScore(institute.getAccessionCount());
}
} }
class CustomRowCallbackHandler implements RowCallbackHandler { class CustomRowCallbackHandler implements RowCallbackHandler {
......
...@@ -24,6 +24,10 @@ import org.apache.commons.lang.ArrayUtils; ...@@ -24,6 +24,10 @@ import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.impl.ClassPK;
import org.genesys2.server.model.impl.Country;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.ElasticService; import org.genesys2.server.service.ElasticService;
import org.genesys2.server.service.FullTextSearchService; import org.genesys2.server.service.FullTextSearchService;
import org.genesys2.server.service.impl.SearchException; import org.genesys2.server.service.impl.SearchException;
...@@ -47,9 +51,15 @@ public class SearchController { ...@@ -47,9 +51,15 @@ public class SearchController {
@Autowired @Autowired
ElasticService searchService; ElasticService searchService;
@Autowired
private ContentService contentService;
@Autowired @Autowired
FullTextSearchService fullTextSearchService; FullTextSearchService fullTextSearchService;
@Autowired
ElasticService accessionSearchService;
@Value("${base.url}") @Value("${base.url}")
private String baseUrl; private String baseUrl;
...@@ -89,8 +99,8 @@ public class SearchController { ...@@ -89,8 +99,8 @@ public class SearchController {
* @throws IOException * @throws IOException
*/ */
@RequestMapping(value = "/acn/opensearch-gossip", produces = MediaType.APPLICATION_JSON_VALUE) @RequestMapping(value = "/acn/opensearch-gossip", produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody public @ResponseBody List<Object> openSearchAutocomplete(ModelMap model, @RequestParam(required = false, value = "q") String searchQuery)
List<Object> openSearchAutocomplete(ModelMap model, @RequestParam(required = false, value = "q") String searchQuery) throws IOException { throws IOException {
List<Object> res = new ArrayList<Object>(); List<Object> res = new ArrayList<Object>();
res.add(searchQuery); res.add(searchQuery);
...@@ -131,16 +141,28 @@ public class SearchController { ...@@ -131,16 +141,28 @@ public class SearchController {
@RequestMapping("/acn/search2") @RequestMapping("/acn/search2")
public String findFullTextDocument(ModelMap model, @RequestParam(required = false, value = "q") String searchQuery, public String findFullTextDocument(ModelMap model, @RequestParam(required = false, value = "q") String searchQuery,
@RequestParam(value = "page", required = false, defaultValue = "1") int page, @RequestParam(value = "page", required = true, defaultValue = "1") int page,
@RequestParam(value = "section", required = false, defaultValue = "Accession") String section) { @RequestParam(value = "section", required = true, defaultValue = "accession") String section) throws SearchException {
ClassPK classPK = contentService.getClassPk(section);
if (classPK == null) {
LOG.warn("No search section " + section);
throw new SearchException("No such search section");
}
model.addAttribute("q", searchQuery); model.addAttribute("q", searchQuery);
model.addAttribute("section", section);
if (!StringUtils.isBlank(searchQuery)) { if (!StringUtils.isBlank(searchQuery)) {
try { try {
final Page<?> x = fullTextSearchService.search(searchQuery, new PageRequest(page - 1, 50), section); if ("accession".equals(section)) {
model.addAttribute("pagedData", x); model.addAttribute("pagedData", accessionSearchService.search(searchQuery, new PageRequest(page - 1, 50)));
model.addAttribute("section", section); } else if ("institute".equals(section)) {
LOG.info("Searching for: " + searchQuery + " returns " + x.getNumberOfElements()); model.addAttribute("pagedData", fullTextSearchService.search(searchQuery, new PageRequest(page - 1, 50), FaoInstitute.class));
} else if ("country".equals(section)) {
model.addAttribute("pagedData", fullTextSearchService.search(searchQuery, new PageRequest(page - 1, 50), Country.class));
} else {
model.addAttribute("pagedData", fullTextSearchService.search(searchQuery, new PageRequest(page - 1, 50), section));
}
} catch (SearchException e) { } catch (SearchException e) {
LOG.info("Searching for: " + searchQuery + " failed with error " + e.getMessage()); LOG.info("Searching for: " + searchQuery + " failed with error " + e.getMessage());
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
......
...@@ -463,6 +463,11 @@ search.search-query-missing=Please enter your search query. ...@@ -463,6 +463,11 @@ search.search-query-missing=Please enter your search query.
search.search-query-failed=Sorry, search failed with error {0} search.search-query-failed=Sorry, search failed with error {0}
search.button.label=Search search.button.label=Search
search.add-genesys-opensearch=Register Genesys Search with your browser search.add-genesys-opensearch=Register Genesys Search with your browser
search.section.accession=Accessions
search.section.article=Content
search.section.activitypost=News Items
search.section.institute=Genebanks
search.section.country=Countries
admin.page.title=Genesys 2 Administration admin.page.title=Genesys 2 Administration
metadata.page.title=Datasets metadata.page.title=Datasets
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
<%@include file="/WEB-INF/jsp/init.jsp"%> <%@include file="/WEB-INF/jsp/init.jsp"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<%@ taglib prefix="mini" tagdir="/WEB-INF/tags/mini"%>
<html> <html>
<head> <head>
...@@ -38,6 +39,7 @@ ...@@ -38,6 +39,7 @@
<div class="applied-filters"> <div class="applied-filters">
<form class="" method="get" action="<c:url value="/acn/search2" />"> <form class="" method="get" action="<c:url value="/acn/search2" />">
<input type="hidden" name="section" value="<c:out value="${section}" />" />
<div class="row"> <div class="row">
<div class="col-md-4"><input type="text" placeholder="<spring:message code="search.input.placeholder" />" name="q" class="form-control" value="<c:out value="${q}" />" /></div> <div class="col-md-4"><input type="text" placeholder="<spring:message code="search.input.placeholder" />" name="q" class="form-control" value="<c:out value="${q}" />" /></div>
<div class="col-md-2"><input type="submit" value="<spring:message code="search.button.label" />" class="btn" /></div> <div class="col-md-2"><input type="submit" value="<spring:message code="search.button.label" />" class="btn" /></div>
...@@ -45,12 +47,12 @@ ...@@ -45,12 +47,12 @@
</form> </form>
</div> </div>
<c:set var="sectionList">Accession,Article,ActivityPost,Country,FaoInstitute</c:set> <c:set var="sectionList">accession,article,activitypost,country,institute</c:set>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<c:forTokens items="${sectionList}" delims="," var="sect"> <c:forTokens items="${sectionList}" delims="," var="sect">
<li class=${sect eq section ? "active" : ""}> <li class=${sect eq section ? "active" : ""}>
<a href="<c:url value="/acn/search2" />?q=${q}&amp;section=${sect}">${sect}</a> <a href="<c:url value="/acn/search2" />?q=${q}&amp;section=${sect}"><spring:message code="search.section.${sect}" /></a>
</li> </li>
</c:forTokens> </c:forTokens>
</ul> </ul>
...@@ -60,8 +62,7 @@ ...@@ -60,8 +62,7 @@
<table class="accessions"> <table class="accessions">
<thead> <thead>
<tr> <tr>
<td class="idx-col"></td> <c:if test="${section eq 'accession'}">
<c:if test="${section eq 'Accession'}">
<td /> <td />
<td><spring:message code="accession.accessionName" /></td> <td><spring:message code="accession.accessionName" /></td>
<td><spring:message code="accession.taxonomy" /></td> <td><spring:message code="accession.taxonomy" /></td>
...@@ -69,30 +70,25 @@ ...@@ -69,30 +70,25 @@
<td class="notimportant"><spring:message code="accession.sampleStatus" /></td> <td class="notimportant"><spring:message code="accession.sampleStatus" /></td>
<td class="notimportant"><spring:message code="accession.holdingInstitute" /></td> <td class="notimportant"><spring:message code="accession.holdingInstitute" /></td>
</c:if> </c:if>
<c:if test="${section eq 'Article'}"> <c:if test="${section eq 'article'}">
<td>Body</td> <td>Body</td>
<td>Summary</td> <td>Summary</td>
</c:if> </c:if>
<c:if test="${section eq 'ActivityPost'}"> <c:if test="${section eq 'activitypost'}">
<td>Body</td> <td>Body</td>
<td>Title</td> <td>Title</td>
</c:if> </c:if>
<c:if test="${section eq 'Country'}"> <c:if test="${section eq 'country'}">
<td>Name</td>
<td>Code</td>
</c:if> </c:if>
<c:if test="${section eq 'FaoInstitute'}"> <c:if test="${section eq 'institute'}">
<td>Full Name</td>
<td>Acronym</td>
</c:if> </c:if>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<c:forEach items="${pagedData.content}" var="unit" varStatus="status"> <c:forEach items="${pagedData.content}" var="unit" varStatus="status">
<tr class="acn"> <tr class="acn">
<td class="idx-col">${status.count + pagedData.size * pagedData.number}</td>
<c:choose> <c:choose>
<c:when test="${section eq 'Accession'}"> <c:when test="${section eq 'accession'}">
<td class="sel" x-aid="${unit.id}"></td> <td class="sel" x-aid="${unit.id}"></td>
<td><a href="<c:url value="/acn/id/${unit.id}" />"><b> <td><a href="<c:url value="/acn/id/${unit.id}" />"><b>
<c:out value="${unit.acceNumb}" /> <c:out value="${unit.acceNumb}" />
...@@ -102,12 +98,19 @@ ...@@ -102,12 +98,19 @@
<td class="notimportant"><spring:message code="accession.sampleStatus.${unit.sampStat}" /></td> <td class="notimportant"><spring:message code="accession.sampleStatus.${unit.sampStat}" /></td>
<td class="notimportant"><a href="<c:url value="/wiews/${unit.institute.code}" />"><c:out value="${unit.institute.code}" /></a></td>