Enabling Search and Indexing
목표 : Guestbook entity의 개수 혹은 Guestbook의 entry의 수가 많아지는 경우, 검색이 가능하도록 하여 이용가능성을 증가시킬 수 있다. 이번 페이지에서는 검색 바를 추가하여 guesetbook의 name 또는 message가 일치하는 결과를 보여줄 수 있도록 구성할 것이다. 그리고 guestbook entry와 guestbook 자체에 indexing을 할 것이다.
※ guestbook 자체에도 indexing을 하는 이유 : 이후에 guestbook entry와 guestbook 자체를 asset-enable 할 것인데, asset-publisher portlet은 asset에 대한 쿼리를 데이터베이스 쿼리가 아니라 search index를 이용해서 쿼리를 한다. 그래서 어떤 entity를 asset-enable 하려면 indexing을 해야한다.
구현 순서
Create an
EntryIndexer
class that extends Liferay’sBaseIndexer
abstract class.Register the
EntryIndexer
in the guestbook-portlet project’sliferay-portlet.xml
file.Update the
addEntry
,updateEntry
, anddeleteEntry
methods ofEntryLocalServiceImpl
to invoke the guestbook entry indexer.Update the Guestbook portlet’s user interface to display a search bar into which users can enter search terms and a JSP to display search results after the search terms are submitted.
Liferay는 Search, Indexing 기능을 제공하기 위해 Lucene (Java search library)를 사용한다. Lucene은 searchable entity를 document라는 단위로 변환하여 작동한다. Guestbook entry에 대한 indexer를 구현하면, 각 entry에 대해 document가 생성된다. Guestbook entry indexer를 구현할 때, entry의 어떤 field가 document에 추가되어야하는지 지정해야한다. 모든 guestbook entry document는 indexer에 추가된다. Lucene index가 검색되었을 때, query와 매치되는 document를 가리키는 pointer가 있는 'hit' object가 리턴된다.
20170504 update
Liferay 내장 Search 포틀릿에서 Custom entity를 검색할 수 있도록 하는 방법에 대한 내용이다. 기본적으로 내장 Search 포틀릿은 out-of-the-box(특별 취급의) asset들에 대한 검색만 가능하다. 즉, 다음과 같은 것들만 검색이 가능하도록 되어있다.
- Users
- Bookmarks
- Bookmark folders
- Blog posts
- Documents and Media files
- Documents and Media folders
- Web Content files
- Web Content folders
- Message Board Messages
- Wiki pages
하지만 우리가 만든 Custom asset에 대해 검색이 가능하도록 할 수 있다. 포틀릿의 Configuration window에서 Setup tab에서 Dispaly Setting panel이 Basic으로 되어있는데, 이것을 Advanced로 바꾸고 아래와 같은 부분을 찾는다.
"values": [
"com.liferay.portal.model.User",
"com.liferay.portlet.bookmarks.model.BookmarksEntry",
"com.liferay.portlet.bookmarks.model.BookmarksFolder",
"com.liferay.portlet.blogs.model.BlogsEntry",
"com.liferay.portlet.documentlibrary.model.DLFileEntry",
"com.liferay.portlet.documentlibrary.model.DLFolder",
"com.liferay.portlet.journal.model.JournalArticle",
"com.liferay.portlet.journal.model.JournalFolder",
"com.liferay.portlet.messageboards.model.MBMessage",
"com.liferay.portlet.wiki.model.WikiPage"
],
이곳의 마지막 라인에 내 Entity를 추가해주면 된다. (ex, com.sdr.metadata.model.Dataset)
그리고 다음으로 내장 Search 포틀릿이 아닌 Custom Search 포틀릿을 만드는 방법을 정리한다.
Liferay Search Query를 실행하기 위해서는 SearchContext
object가 필요하다. 이 객체에는 locale, company instance, user, timezone등 많은 detail들이 들어가 있는데 전부 다루기 힘드니까 SearchContextFactory의 getInstance(HttpServletRequest request) 메소드를 이용하여 searchContext를 얻는다.
SearchContext searchContext = SearchContextFactory.getInstance(request);
searchContext.setKeywords(keywords);
searchContext.setAttribute("paginationType", "more");
searchContext.setStart(0);
searchContext.setEnd(10);
// Query가 담겨있는 keywords, Pagination Type을 정하는 부분 등을 포함시킬 수 있다.
다음은 우리가 포틀릿에 등록했던 Indexer(e.g., EntryIndexer)와 위의 SearchContext를 이용해서 Search를 진행할 것이다. Search Query를 통해 search하는 방법은 두가지가 있는데,
- Indexer의 search method를 이용
- SearchEngineUtil.search method를 이용 이다.
※ SearchEngineUtil은 모든 Search와 관련된 것들을 다루는 클래스라 debug level logging을 하면 도움이 될 수 있다.
어쨌든 두가지 방법의 결과는 Hits
object이다. Hits 객체는 query 결과에 매칭되는 Lucene document를 가지고 있다. 두가지 Search 방법의 차이는 두번째 방법은 search query를 개인적으로 만들어야하고 search context도 스스로 적절하게 구성해야한다는 것이다. Liferay의 Indexer는 특정 entity에 연관되어 있다(우리가 GuestbookIndexer, EntryIndexer 등을 개별적으로 구현했듯이). 그래서 Indexer를 이용한 search를 하면 그 특정 entity에 관련된 것만 서치가 가능하다. 하지만 SearchEngineUtil.search 메소드를 이용하면 이것을 우회할 수 있다. searchContext.setEntryClassNames 메소드를 통해 특정 entity를 직접 설정이 가능하고 검색의 scope 또한 searchContext.setCompanyId와 searchContext.setGroupIds를 통해 지정할 수 있다.
SearchEngineUtil.search method의 가장 기본적인 형태는 SearchEngineUtil.search(SearchContext searchContext, Query query)
이다. Liferay는 Query interface를 확장해서 BooleanQuery, TermRangeQuery, TermQuery
를 제공한다. Factory 클래스를 이용하여 query implementation class를 instantiate할 수 있다.
예를 들어, title이라는 필드에서 liferay라는 term을 가진 document를 검색하고 싶으면 다음과 같이 하면 된다.
TermQuery termQuery = TermQueryFactoryUtil.create(searchContext, "title", "liferay");
try {
Hits hits = SearchEngineUtil.search(searchContext, termQuery);
}
catch (SearchException se) {
// handle search exception
}
만약 특정 range 사이에서의 검색을 원한다면 TermRangeQuery를 이용하여 검색이 가능하다. 예를 들어 2014년 12월 4일에 수정된 document를 찾고 싶다면 다음과 같이 구현이 가능하다.
TermRangeQuery termRangeQuery = TermRangeQueryFactoryUtil.create(searchContext, "modified", "201412040000000", "201412050000000", true, true);
try {
Hits hits = SearchEngineUtil.search(searchContext, termRangeQuery);
}
catch (SearchException se) {
// handle search exception
}
더 복잡한 Query를 만들고 싶다면 BooleanQuery를 이용하여 구현이 가능하다. 예를 들어,
- title이라는 필드는 liferay라는 term을 가지고 있고,
- description이라는 필드는 lucene이라는 term을 가지고 있지 않고,
- 2014년 12월 4일에 수정된
document를 검색하고 싶다면 다음과 같이 구현이 가능하다.
TermQuery termQuery1 = TermQueryFactoryUtil.create(searchContext, "title", "liferay");
TermQuery termQuery2 = TermQueryFactoryUtil.create(searchContext, "description", "lucene");
TermRangeQuery termRangeQuery = TermRangeQueryFactoryUtil.create(searchContext, "modified", "201412040000000", "201412050000000", true, false);
BooleanQuery booleanQuery = BooleanQueryFactoryUtil.create(searchContext);
booleanQuery.add(termQuery1, BooleanClauseOccur.MUST);
booleanQuery.add(termQuery2, BooleanClauseOccur.MUST_NOT);
booleanQuery.add(termRangeQuery, BooleanClauseOccur.MUST);
try {
Hits hits = SearchEngineUtil.search(searchContext, booleanQuery);
}
catch (SearchException se) {
// handle search exception
}
마지막으로 StringQuery
를 제공하는데 Lucene의 Query syntax를 이용하는 방법이다. 이것은 Facet search part에서 좀 더 자세히 살펴보겠다.
Search의 결과로 Hits object가 return이 되면 해당 객체가 가지고 있는 Lucene document 목록을 받아올 수 있다.
Document[] docs = hits.getDocs(); // or
List<Document> docs = hits.toList();
다음 5가지 overriding method의 정체
- postProcessContextQuery(BooleanQuery booleanQuery, SearchContext searchcontext)
- postProcessDocument(Document document, Object object)
- postProcessFullQuery(BooleanQuery fullQuery, SearchContext searchcontext)
- postProcessSearchQuery(BooleanQuery searchquery, SearchContext searchcontext)
- postProcessSummary(Summary summary, Document document, Locale locale, String snippet, PortletURL portletURL)