Commit fe8042c9 authored by Matija Obreza's avatar Matija Obreza

@Indexed some entities, Lucene full-text search

parent e5047601
......@@ -5,3 +5,4 @@ out
target
logs
/bin
/lucene
......@@ -360,6 +360,11 @@
<artifactId>sitemesh</artifactId>
<version>3.0-alpha-2</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-search</artifactId>
<version>4.3.0.Final</version>
</dependency>
</dependencies>
<build>
......
......@@ -23,11 +23,14 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.hibernate.search.annotations.DocumentId;
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class BusinessModel implements HibernateModel {
@Id
@DocumentId
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", unique = true, nullable = false, length = 20)
protected Long id;
......
......@@ -30,10 +30,15 @@ import javax.persistence.Table;
import org.crophub.rest.common.model.impl.Country;
import org.crophub.rest.common.model.impl.FaoInstitute;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
// @Table(name = "accession")
@Table(name = "accession")
@Indexed
public class Accession implements java.io.Serializable {
private static final long serialVersionUID = -7630113633534038876L;
private Long id;
......@@ -61,6 +66,7 @@ public class Accession implements java.io.Serializable {
@Id
@GeneratedValue
@Column(name = "id", unique = true, nullable = false)
@DocumentId
public Long getId() {
return this.id;
}
......@@ -89,6 +95,7 @@ public class Accession implements java.io.Serializable {
}
@Column(name = "ACC_Numb_HI", nullable = false, length = 128)
@Field(name="title", store=Store.NO)
public String getAccessionName() {
return this.accNumbHi;
}
......
......@@ -29,11 +29,17 @@ import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
/**
* Metadata generated by hbm2java
*/
@Entity
@Table(name = "metadata")
@Indexed
public class Metadata implements java.io.Serializable {
/**
......@@ -76,6 +82,7 @@ public class Metadata implements java.io.Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "Meta_id")
@DocumentId
public Long getId() {
return this.id;
}
......@@ -94,6 +101,7 @@ public class Metadata implements java.io.Serializable {
}
@Column(nullable = false, length = 128)
@Field(name="title", store=Store.NO)
public String getTitle() {
return this.title;
}
......@@ -121,6 +129,7 @@ public class Metadata implements java.io.Serializable {
}
@Column(length = 128)
@Field(store=Store.NO)
public String getLocation() {
return this.location;
}
......@@ -157,6 +166,7 @@ public class Metadata implements java.io.Serializable {
}
@Lob
@Field(store=Store.NO)
public String getCitation() {
return this.citation;
}
......@@ -166,6 +176,7 @@ public class Metadata implements java.io.Serializable {
}
@Lob
@Field(name="body", store=Store.NO)
public String getDescription() {
return this.description;
}
......
......@@ -25,9 +25,15 @@ import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "all_taxonomy", uniqueConstraints = { @UniqueConstraint(columnNames = {
"genus", "species" }) })
@Indexed
public class Taxonomy implements java.io.Serializable {
/**
......@@ -48,6 +54,7 @@ public class Taxonomy implements java.io.Serializable {
}
@Id
@DocumentId
@GeneratedValue
@Column(name="Taxon_Code", unique = true, nullable = false)
public Long getId() {
......@@ -77,6 +84,7 @@ public class Taxonomy implements java.io.Serializable {
}
@Column(name="Taxon_Name", nullable = false, length = 128)
@Field(name="title", store=Store.NO)
public String getTaxonName() {
return this.taxonName;
}
......
......@@ -14,7 +14,6 @@
* limitations under the License.
**/
package org.crophub.rest.common.model.impl;
import java.util.Calendar;
......@@ -27,17 +26,23 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.crophub.rest.common.model.BusinessModel;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "activitypost")
@Indexed
public class ActivityPost extends BusinessModel {
private static final long serialVersionUID = 8690395020204070378L;
@Column(nullable = false, length = 500)
@Field(name="title", store = Store.NO)
private String title;
@Lob
@Field(name="body", store = Store.NO)
private String body;
@Temporal(TemporalType.TIMESTAMP)
......
......@@ -27,9 +27,13 @@ import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.crophub.rest.common.model.BusinessModel;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "article")
@Indexed
public class Article extends BusinessModel {
private static final long serialVersionUID = 8690395020204070378L;
......@@ -38,9 +42,11 @@ public class Article extends BusinessModel {
private String slug;
@Lob
@Field(name="title", store=Store.NO)
private String title;
@Lob
@Field(name="body", store=Store.NO)
private String body;
@Temporal(TemporalType.TIMESTAMP)
......
......@@ -25,8 +25,14 @@ import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "country")
@Indexed
public class Country implements java.io.Serializable {
private static final long serialVersionUID = -1688723909298769804L;
......@@ -42,6 +48,7 @@ public class Country implements java.io.Serializable {
}
@Id
@DocumentId
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(unique = true, nullable = false)
public Long getId() {
......@@ -53,6 +60,7 @@ public class Country implements java.io.Serializable {
}
@Column(nullable = false, unique = true, length = 3)
@Field(name="title")
public String getCode3() {
return code3;
}
......@@ -80,6 +88,7 @@ public class Country implements java.io.Serializable {
}
@Column(unique = false, nullable = false, length = 200)
@Field(name="body", store=Store.NO)
public String getName() {
return this.name;
}
......
......@@ -14,7 +14,6 @@
* limitations under the License.
**/
package org.crophub.rest.common.model.impl;
import java.util.List;
......@@ -27,9 +26,13 @@ import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.crophub.rest.common.model.BusinessModel;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "crop")
@Indexed
public class Crop extends BusinessModel {
private static final long serialVersionUID = -2686341831839109257L;
......@@ -43,9 +46,11 @@ public class Crop extends BusinessModel {
private String shortName;
@Column(nullable = false, length = 200)
@Field(name="title", store = Store.NO)
private String name;
@Lob
@Field(name="body", store = Store.NO)
private String description;
/**
......
......@@ -29,10 +29,15 @@ import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import org.hibernate.annotations.Index;
import org.hibernate.search.annotations.DocumentId;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.Store;
@Entity
@Table(name = "faoinstitute", uniqueConstraints = @UniqueConstraint(columnNames = { "code" }))
@org.hibernate.annotations.Table(appliesTo = "faoinstitute", indexes = { @Index(columnNames = { "code" }, name = "code_FAOINSTITUTE") })
@Indexed
public class FaoInstitute extends GeoEntity implements java.io.Serializable {
/**
......@@ -67,6 +72,7 @@ public class FaoInstitute extends GeoEntity implements java.io.Serializable {
}
@Id
@DocumentId
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
......@@ -77,6 +83,7 @@ public class FaoInstitute extends GeoEntity implements java.io.Serializable {
}
@Column(unique = true, nullable = false, length = 8)
@Field(store = Store.NO)
public String getCode() {
return this.code;
}
......@@ -86,6 +93,7 @@ public class FaoInstitute extends GeoEntity implements java.io.Serializable {
}
@Column(length = 300)
@Field(name="title", store = Store.YES)
public String getFullName() {
return this.fullName;
}
......@@ -152,5 +160,4 @@ public class FaoInstitute extends GeoEntity implements java.io.Serializable {
public void setAccessionCount(long accessionCount) {
this.accessionCount = accessionCount;
}
}
package org.crophub.rest.common.service;
import org.hibernate.search.SearchException;
import org.springframework.data.domain.Page;
public interface SearchService {
Page<?> search(String searchQuery, String[] fields, int startAt, int maxResults) throws SearchException;
}
package org.crophub.rest.common.service.impl;
import javax.persistence.EntityManagerFactory;
import org.apache.commons.lang.time.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Session;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Component
public class LuceneIndexer {
public static final Log LOG = LogFactory.getLog(LuceneIndexer.class);
@Autowired
private EntityManagerFactory entityManagerFactory;
@PreAuthorize("hasRole('ADMINISTRATOR')")
public void reindexEverything() {
LOG.info("Indexing everything in the DB...");
StopWatch timer = new StopWatch();
Session session = (Session) this.entityManagerFactory.createEntityManager().getDelegate();
FullTextSession fullTextSession = Search.getFullTextSession(session);
try {
timer.start();
fullTextSession.createIndexer().startAndWait();
timer.stop();
} catch (InterruptedException e) {
LOG.error(e);
} finally {
fullTextSession.close();
}
LOG.info("Finished indexing after: " + timer.getTime() + "ms");
}
}
\ No newline at end of file
package org.crophub.rest.common.service.impl;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
public class LucenePage<T> implements Page<T> {
private final int number;
private final int size;
private long totalElements;
private List<T> content;
public LucenePage(int number, int size) {
this.number = number;
this.size = size;
}
@Override
public List<T> getContent() {
return content;
}
@Override
public int getNumber() {
return number;
}
@Override
public int getNumberOfElements() {
return content.size();
}
@Override
public int getSize() {
return size;
}
@Override
public Sort getSort() {
return null;
}
@Override
public long getTotalElements() {
return totalElements;
}
public void setTotalElements(long totalElements) {
this.totalElements = totalElements;
}
@Override
public int getTotalPages() {
return (int) Math.ceil((double) totalElements / (double) size);
}
@Override
public boolean hasContent() {
return content != null && content.size() > 0;
}
@Override
public boolean hasNextPage() {
return content.size() == size;
}
@Override
public boolean hasPreviousPage() {
return number > 1;
}
@Override
public boolean isFirstPage() {
return number == 0;
}
@Override
public boolean isLastPage() {
return content.size() < size;
}
@Override
public Iterator<T> iterator() {
return content.iterator();
}
public void setContent(List<T> data) {
content = data;
}
}
package org.crophub.rest.common.service.impl;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.lucene.util.Version;
import org.crophub.rest.common.service.SearchService;
import org.hibernate.search.SearchException;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class SearchServiceImpl implements SearchService {
public static final Log LOG = LogFactory.getLog(LuceneIndexer.class);
@PersistenceContext
private EntityManager entityManager;
/**
* @return
* @throws SearchException
*/
@Override
public Page<?> search(String searchQuery, String[] fields, int page, int pageSize) throws SearchException {
LOG.debug("Searching for: " + searchQuery);
LucenePage<Object> lucenePage = new LucenePage<Object>(page, pageSize);
if (searchQuery == null)
return null;
FullTextEntityManager ftEm = Search.getFullTextEntityManager(this.entityManager);
org.apache.lucene.search.Query luceneQuery = null;
try {
luceneQuery = getLuceneQuery(searchQuery, fields);
org.hibernate.search.jpa.FullTextQuery query = ftEm.createFullTextQuery(luceneQuery);
lucenePage.setTotalElements(query.getResultSize());
@SuppressWarnings("unchecked")
List<Object> data = query.setMaxResults(pageSize).setFirstResult(page * pageSize).getResultList();
lucenePage.setContent(data);
return lucenePage;
} catch (ParseException e) {
throw new SearchException(e);
}
}
/**
* @param searchQuery
* @return
* @throws ParseException
*/
private Query getLuceneQuery(String searchQuery, String[] fields) throws ParseException {
MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_36, fields, new StandardAnalyzer(Version.LUCENE_36));
try {
return parser.parse(searchQuery);
} catch (ParseException e) {
return null;
}
}
}
......@@ -14,7 +14,6 @@
* limitations under the License.
**/
package org.crophub.rest.servlet.controller;
import java.io.IOException;
......@@ -23,6 +22,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.crophub.rest.common.service.GeoService;
import org.crophub.rest.common.service.impl.InstituteUpdater;
import org.crophub.rest.common.service.impl.LuceneIndexer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
......@@ -36,6 +36,9 @@ public class AdminController {
@Autowired
InstituteUpdater instituteUpdater;
@Autowired
LuceneIndexer luceneIndexer;
@Autowired
GeoService geoService;
......@@ -64,4 +67,10 @@ public class AdminController {
return "redirect:/admin/";
}
@RequestMapping(method = RequestMethod.POST, value = "/reindexEverything")
public String reindexEverything() {
luceneIndexer.reindexEverything();
return "redirect:/admin/";
}
}
/**
* Copyright 2013 Global Crop Diversity Trust
*
* 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.
**/
package org.crophub.rest.servlet.controller;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.crophub.rest.common.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/search")