/* * Copyright 2011-2013 the original author or authors. * * 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 kr.debop4j.search.tools; import com.google.common.collect.Sets; import kr.debop4j.core.Action1; import kr.debop4j.core.Guard; import kr.debop4j.core.parallelism.Parallels; import kr.debop4j.core.tools.StringTool; import kr.debop4j.data.hibernate.tools.HibernateTool; import kr.debop4j.search.QueryMethod; import kr.debop4j.search.SearchParameter; import kr.debop4j.search.SearchRangeParameter; import kr.debop4j.search.dao.HibernateSearchDao; import kr.debop4j.search.dao.IHibernateSearchDao; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.SimpleAnalyzer; import org.apache.lucene.index.Term; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.*; import org.apache.lucene.util.Version; import org.hibernate.SessionFactory; import org.hibernate.event.spi.EventType; import org.hibernate.metadata.ClassMetadata; import org.hibernate.search.FullTextQuery; import org.hibernate.search.FullTextSession; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.event.impl.FullTextIndexEventListener; import org.hibernate.search.query.dsl.QueryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Set; /** * Hibernate Search 관련 Utility class s * * @author 배성혁 ( sunghyouk.bae@gmail.com ) * @since 13. 2. 28. */ public class SearchTool { private static final Logger log = LoggerFactory.getLogger(SearchTool.class); private SearchTool() {} /** * Hibernate-Search의 FullTextIndexEventListener를 SessionFactory에 등록합니다. * * @param sessionFactory the session factory * @param listener the listener */ public static void registerFullTextIndexEventListener(SessionFactory sessionFactory, FullTextIndexEventListener listener) { assert sessionFactory != null; log.info("sessionFactory에 FullTestIndexEventListener를 등록합니다... listener=[{}]", listener); try { HibernateTool.registerEventListener(sessionFactory, listener, EventType.POST_UPDATE, EventType.POST_INSERT, EventType.POST_DELETE, EventType.FLUSH); } catch (Throwable t) { log.warn("listener를 등록하는데 실패했습니다. 단 이미 등록된 경우에는 무시해도 됩니다.", t); } } /** * 루씬용 Query를 빌드합니다. * * @param fts the fts * @param clazz the clazz * @param fieldName the field name * @param values the values * @return the org . apache . lucene . search . query */ public static Query bulidLuceneQuery(FullTextSession fts, Class<?> clazz, String fieldName, String values) { log.trace("루씬 쿼리를 빌드합니다. clazz=[{}], fieldName=[{}], values=[{}]", clazz, fieldName, values); Analyzer analyzer; if (clazz == null) { analyzer = new SimpleAnalyzer(Version.LUCENE_36); } else { analyzer = fts.getSearchFactory().getAnalyzer(clazz); } QueryParser parser = new QueryParser(Version.LUCENE_36, fieldName, analyzer); Query luceneQuery = null; try { luceneQuery = parser.parse(values); } catch (ParseException e) { throw new IllegalArgumentException("Unable to parse search entry into a Lucene query", e); } return luceneQuery; } /** * FullTextQuery 인스턴스를 생성합니다. * * @param fts FullTextSession instance. * @param luceneQuery the lucene query * @param clazz the clazz * @return the full text query */ public static FullTextQuery createFullTextQuery(FullTextSession fts, Query luceneQuery, Class<?> clazz) { FullTextQuery ftq = fts.createFullTextQuery(luceneQuery, clazz); // 기본값이 SKIP, QUERY 입니다. // ftq.initializeObjectsWith(ObjectLookupMethod.SKIP, DatabaseRetrievalMethod.QUERY); return ftq; } /** * 인덱싱된 엔티티의 수형을 반환합니다. * * @param sessionFactory the session factory * @return 인덱싱된 엔티티의 수형들 */ public static Set<Class> getIndexedClasses(SessionFactory sessionFactory) { if (log.isDebugEnabled()) log.debug("매핑된 엔티티중에 인덱싱을 수행할 엔티티들을 조회합니다."); final Set<Class> classes = Sets.newHashSet(); Collection<ClassMetadata> metadatas = sessionFactory.getAllClassMetadata().values(); for (ClassMetadata meta : metadatas) { Class clazz = meta.getMappedClass(); if (clazz.getAnnotation(Indexed.class) != null) { classes.add(clazz); log.trace("인덱싱된 엔티티=[{}]", clazz); } } return classes; } /** * 모든 엔티티의 인덱스를 재구성합니다. * see {@link SearchTool#getIndexedClasses(org.hibernate.SessionFactory)} * * @param sessionFactory SessionFactory 인스턴스 * @param classes 인덱싱을 수행할 대상 엔티티의 수형 * @param clear 기존 인덱스를 삭제할 것인가 여부 */ public static void indexAll(final SessionFactory sessionFactory, final Set<Class> classes, final boolean clear) { if (log.isDebugEnabled()) log.debug("모든 엔티티에 대해 전체 인덱싱을 수행합니다. classes=[{}], clear=[{}]", StringTool.listToString(classes), clear); final IHibernateSearchDao searchDao = new HibernateSearchDao(); for (Class clazz : classes) { if (clear) searchDao.clearIndex(clazz); searchDao.indexAll(clazz, 100); } } /** * 모든 엔티티의 인덱스를 병렬 방식으로 재구성합니다. * see {@link SearchTool#getIndexedClasses(org.hibernate.SessionFactory)} * * @param sessionFactory SessionFactory 인스턴스 * @param classes 인덱싱을 수행할 대상 엔티티의 수형 * @param clear 기존 인덱스를 삭제할 것인가 여부 */ public static void indexAllAsParallel(final SessionFactory sessionFactory, final Set<Class> classes, final boolean clear) { if (log.isDebugEnabled()) log.debug("병렬 방식으로 모든 엔티티에 대해 전체 인덱싱을 수행합니다. classes=[{}], clear=[{}]", StringTool.listToString(classes), clear); Parallels.runEach(classes, new Action1<Class>() { @Override public void perform(Class clazz) { IHibernateSearchDao searchDao = new HibernateSearchDao(); if (clear) searchDao.clearIndex(clazz); searchDao.indexAll(clazz, 100); } }); } /** * 루씬 검색용 QueryBuilder 를 생성합니다. * * @param clazz 검색할 엔티티 수형 * @param fts FullTextSession instance * @param parameters 사용할 파라미터 정보 * @return {@link QueryBuilder} instance. */ public static QueryBuilder buildLuceneQuery(final Class<?> clazz, final FullTextSession fts, SearchParameter... parameters) { Guard.shouldNotBeNull(fts, "fts"); QueryBuilder builder = fts.getSearchFactory().buildQueryBuilder().forEntity(clazz).get(); for (SearchParameter sp : parameters) { log.trace("QueryBulider에 다음 parameter를 추가합니다... SearchParameter=[{}]", sp); QueryMethod queryMethod = sp.getQueryMethod(); switch (queryMethod) { case Term: builder.keyword().onField(sp.getName()).matching(sp.getValue()); break; case Phrase: builder.phrase().onField(sp.getName()).sentence(sp.getValue()); break; case Wildcard: final WildcardQuery wildcardQuery = new WildcardQuery(new Term(sp.getName(), sp.getValue())); builder.bool().should(wildcardQuery); break; case Prefix: final PrefixQuery prefixQuery = new PrefixQuery(new Term(sp.getName(), sp.getValue())); builder.bool().should(prefixQuery); break; case Fuzzy: final FuzzyQuery query = new FuzzyQuery(new Term(sp.getName(), sp.getValue())); builder.bool().should(query); break; case Range: SearchRangeParameter srp = (SearchRangeParameter) sp; builder.range().onField(sp.getName()).from(srp.getFrom()).to(srp.getTo()); break; case Boolean: default: final TermQuery termQuery = new TermQuery(new Term(sp.getName(), sp.getValue())); builder.bool().should(termQuery); break; } } return builder; } }