org.opentaps.search.HibernateSearchRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.opentaps.search.HibernateSearchRepository.java

Source

/*
 * Copyright (c) Open Source Strategies, Inc.
 *
 * Opentaps is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Opentaps is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Opentaps.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.opentaps.search;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.lucene.analysis.KeywordAnalyzer;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Query;
import org.hibernate.CacheMode;
import org.hibernate.Transaction;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.ofbiz.base.util.Debug;
import org.opentaps.domain.search.SearchRepositoryInterface;
import org.opentaps.domain.search.SearchResult;
import org.opentaps.foundation.entity.hibernate.Session;
import org.opentaps.foundation.infrastructure.InfrastructureException;
import org.opentaps.foundation.repository.RepositoryException;
import org.opentaps.foundation.repository.ofbiz.Repository;

/**
 * Implementation of the search repository using Hibernate search.
 */
public class HibernateSearchRepository extends Repository implements SearchRepositoryInterface {

    /** The default placeholder in Lucene queries. */
    public static final String DEFAULT_PLACEHOLDER = "?";
    /** The regular expression recognizing the default placeholder in Lucene queries. */
    public static final String DEFAULT_PLACEHOLDER_REGEX = "\\?";

    private static final String MODULE = HibernateSearchRepository.class.getName();

    private List<SearchResult> results;
    private int resultSize = 0;

    /** {@inheritDoc} */
    public String makeQueryString(String queryString, String keywords) {
        return makeQueryString(queryString, DEFAULT_PLACEHOLDER_REGEX, keywords);
    }

    /**
     * Inserts user given keywords into a Lucene query.
     * The user given keywords are escaped as to not cause any parsing exception.
     * @param queryString a Lucene query with placeholders
     * @param placeholderRegex a regular expression matching the placeholder used
     * @param keywords a user given string of the keywords he is looking for
     * @return the Lucene query string
     */
    private String makeQueryString(String queryString, String placeholderRegex, String keywords) {
        // escape lucene special chars
        String s = QueryParser.escape(keywords.toLowerCase());
        // tokenized fields will never a match multiword string, join the results
        String[] ss = s.split(" +");
        // this ( groups one query
        StringBuilder sb = new StringBuilder("(");
        for (int i = 0; i < ss.length; i++) {
            // group and quote the keywords
            s = "(\"" + ss[i] + "\")";
            sb.append(queryString.replaceAll(placeholderRegex, s));
            if (i + 1 < ss.length) {
                // join multiple word as multiple ANDed queries
                sb.append(") AND (");
            }
        }
        // close the last query
        sb.append(")");
        return sb.toString();
    }

    /** {@inheritDoc} */
    public void searchInEntities(Set<Class<?>> entityClasses, String queryString, int pageStart, int pageSize)
            throws RepositoryException {

        Debug.logInfo("searchInEntities: [" + entityClasses + "] query [" + queryString + "] page start ["
                + pageStart + "] pageSize [" + pageSize + "]", MODULE);
        Session session = null;
        FullTextSession fullTextSession = null;
        Transaction tx = null;
        try {
            session = getInfrastructure().getSession();
            fullTextSession = Search.getFullTextSession(session.getHibernateSession());
            fullTextSession.setCacheMode(CacheMode.IGNORE);
            tx = fullTextSession.beginTransaction();
            // create a native Lucene query
            QueryParser parser = new QueryParser("_hibernate_class", new KeywordAnalyzer());
            Query luceneQuery = parser.parse(queryString);
            // create the full text query
            FullTextQuery fullTextQuery = fullTextSession.createFullTextQuery(luceneQuery,
                    entityClasses.toArray(new Class[entityClasses.size()]));
            fullTextQuery.setProjection(new String[] { FullTextQuery.OBJECT_CLASS, FullTextQuery.ID });
            // perform the query
            List<Object[]> queryResults = fullTextQuery.list();
            resultSize = fullTextQuery.getResultSize();
            Debug.logInfo("searchInEntities: found " + resultSize + " total results.", MODULE);
            Debug.logInfo("searchInEntities: list contains " + queryResults.size() + " results.", MODULE);

            // a Verbose debug of all results (in current pagination range)
            // and map the query results into SearchResult objects
            results = new ArrayList<SearchResult>();
            Debug.logInfo("------- end of results (total " + resultSize + ") -------", MODULE);
            int i = pageStart + 1;
            for (Object[] o : queryResults) {
                StringBuilder sb = new StringBuilder(" ").append(i).append(" --> ");
                for (Object detail : o) {
                    sb.append(detail).append(" ");
                }
                Debug.logInfo(sb.toString(), MODULE);
                i++;

                results.add(new SearchResult((Class<?>) o[0], o[1]));

            }
            Debug.logInfo("------- end of results (total " + resultSize + ") -------", MODULE);

            tx.commit();
        } catch (ParseException e) {
            Debug.logError(e, "Error parsing lucene query [" + queryString + "].", MODULE);
            // rollback the transaction
            if (tx != null) {
                try {
                    tx.rollback();
                } catch (Exception e2) {
                    Debug.logWarning(e2, "Could not rollback the hibernate transaction on error.", MODULE);
                }
            }
            throw new RepositoryException(e);
        } catch (InfrastructureException e) {
            Debug.logError(e, "Error getting the hibernate session.", MODULE);
            // rollback the transaction
            if (tx != null) {
                try {
                    tx.rollback();
                } catch (Exception e2) {
                    Debug.logWarning(e2, "Could not rollback the hibernate transaction on error.", MODULE);
                }
            }
            throw new RepositoryException(e);
        } finally {
            // close the sessions
            try {
                if (fullTextSession != null) {
                    fullTextSession.close();
                }
            } catch (Exception e) {
                Debug.logWarning(e, "Could not close the FullTextSession.", MODULE);
            }

            try {
                if (session != null && session.isOpen()) {
                    Debug.logWarning("Session still open, closing.", MODULE);
                    session.close();
                }
            } catch (Exception e) {
                Debug.logWarning(e, "Could not close the Session.", MODULE);
            }
        }
    }

    /** {@inheritDoc} */
    public List<SearchResult> getResults() {
        return results;
    }

    /** {@inheritDoc} */
    public int getResultSize() {
        return resultSize;
    }

}