org.yestech.lib.hibernate.search.YesHibernateSearchTemplate.java Source code

Java tutorial

Introduction

Here is the source code for org.yestech.lib.hibernate.search.YesHibernateSearchTemplate.java

Source

/*
 * Copyright LGPL3
 * YES Technology Association
 * http://yestech.org
 *
 * http://www.opensource.org/licenses/lgpl-3.0.html
 */

/*
 *
 * Author:  Artie Copeland
 * Last Modified Date: $DateTime: $
 */
package org.yestech.lib.hibernate.search;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.queryParser.MultiFieldQueryParser;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.*;
import org.hibernate.*;
import org.hibernate.Query;
import org.hibernate.Filter;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.event.EventSource;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextFilter;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;

/**
 * A wrapper for Hibernate Search.  Modeled after HibernateTemplate.
 *
 * @author Artie Copeland
 * @version $Revision: $
 */
@SuppressWarnings({ "unchecked" })
public class YesHibernateSearchTemplate extends HibernateAccessor implements YesHibernateSearchOperations {

    private boolean alwaysUseNewSession = false;

    private boolean exposeNativeSession = false;

    public YesHibernateSearchTemplate() {
    }

    public YesHibernateSearchTemplate(SessionFactory sessionFactory) {
        setSessionFactory(sessionFactory);
        afterPropertiesSet();
    }

    /**
     * Set whether to always use a new Hibernate Session for this template.
     * Default is "false"; if activated, all operations on this template will
     * work on a new Hibernate Session even in case of a pre-bound Session
     * (for example, within a transaction or OpenSessionInViewFilter).
     * <p>Within a transaction, a new Hibernate Session used by this template
     * will participate in the transaction through using the same JDBC
     * Connection. In such a scenario, multiple Sessions will participate
     * in the same database transaction.
     * <p>Turn this on for operations that are supposed to always execute
     * independently, without side effects caused by a shared Hibernate Session.
     */
    public void setAlwaysUseNewSession(boolean alwaysUseNewSession) {
        this.alwaysUseNewSession = alwaysUseNewSession;
    }

    /**
     * Return whether to always use a new Hibernate Session for this template.
     */
    public boolean isAlwaysUseNewSession() {
        return this.alwaysUseNewSession;
    }

    /**
     * Set whether to expose the native Hibernate Session to
     * HibernateCallback code.
     * <p>Default is "false": a Session proxy will be returned, suppressing
     * <code>close</code> calls and automatically applying query cache
     * settings and transaction timeouts.
     *
     * @see org.springframework.orm.hibernate3.HibernateCallback
     * @see org.hibernate.Session
     */
    public void setExposeNativeSession(boolean exposeNativeSession) {
        this.exposeNativeSession = exposeNativeSession;
    }

    /**
     * Return whether to expose the native Hibernate Session to
     * HibernateCallback code, or rather a Session proxy.
     */
    public boolean isExposeNativeSession() {
        return this.exposeNativeSession;
    }

    /**
     * Execute the action specified by the given action object within a
     * native {@link org.hibernate.Session}.
     * <p>This execute variant overrides the template-wide
     * {@link #isExposeNativeSession() "exposeNativeSession"} setting.
     *
     * @param action callback object that specifies the Hibernate action
     * @return a result object returned by the action, or <code>null</code>
     * @throws org.springframework.dao.DataAccessException
     *          in case of Hibernate errors
     */
    public Object executeWithNativeSession(HibernateSearchCallback action) {
        return doExecute(action, false, true);
    }

    /**
     * Execute the action specified by the given action object within a Session.
     *
     * @param action               callback object that specifies the Hibernate action
     * @param enforceNewSession    whether to enforce a new Session for this template
     *                             even if there is a pre-bound transactional Session
     * @param enforceNativeSession whether to enforce exposure of the native
     *                             Hibernate Session to callback code
     * @return a result object returned by the action, or <code>null</code>
     * @throws org.springframework.dao.DataAccessException
     *          in case of Hibernate errors
     */
    public Object doExecute(HibernateSearchCallback action, boolean enforceNewSession, boolean enforceNativeSession)
            throws DataAccessException {

        Assert.notNull(action, "Callback object must not be null");

        Session session = (enforceNewSession
                ? SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor())
                : getSession());
        boolean existingTransaction = (!enforceNewSession
                && (SessionFactoryUtils.isSessionTransactional(session, getSessionFactory())));
        if (existingTransaction) {
            logger.debug("Found thread-bound Session for HibernateTemplate");
        }

        FlushMode previousFlushMode = null;
        try {
            previousFlushMode = applyFlushMode(session, existingTransaction);
            enableFilters(session);
            Session sessionToExpose = (enforceNativeSession || isExposeNativeSession() ? session
                    : createSessionProxy(session));
            Object result = action.doInHibernate(sessionToExpose);
            flushIfNecessary(session, existingTransaction);
            return result;
        } catch (HibernateException ex) {
            throw convertHibernateAccessException(ex);
        } finally {
            if (existingTransaction) {
                logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
                disableFilters(session);
                if (previousFlushMode != null) {
                    session.setFlushMode(previousFlushMode);
                }
            } else {
                // Never use deferred close for an explicitly new Session.
                if (isAlwaysUseNewSession()) {
                    SessionFactoryUtils.closeSession(session);
                } else {
                    if (SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) {
                        SessionFactoryUtils.initDeferredClose(getSessionFactory());
                    } else {
                        SessionFactoryUtils.closeSession(session);
                    }

                }
            }
        }
    }

    /**
     * Create a close-suppressing proxy for the given Hibernate Session.
     * The proxy also prepares returned Query and Criteria objects.
     * @param session the Hibernate Session to create a proxy for
     * @return the Session proxy
     * @see org.hibernate.Session#close()
     */
    protected Session createSessionProxy(Session session) {
        Class[] sessionIfcs = null;
        Class mainIfc = (session instanceof org.hibernate.classic.Session ? org.hibernate.classic.Session.class
                : Session.class);
        if (session instanceof EventSource) {
            sessionIfcs = new Class[] { mainIfc, EventSource.class };
        } else if (session instanceof SessionImplementor) {
            sessionIfcs = new Class[] { mainIfc, SessionImplementor.class };
        } else {
            sessionIfcs = new Class[] { mainIfc };
        }
        return (Session) Proxy.newProxyInstance(session.getClass().getClassLoader(), sessionIfcs,
                new CloseSuppressingInvocationHandler(session));
    }

    /**
     * Return a Session for use by this template.
     * <p>Returns a new Session in case of "alwaysUseNewSession" (using the same
     * JDBC Connection as a transactional Session, if applicable), a pre-bound
     * Session in case of "allowCreate" turned off, and a pre-bound or new Session
     * otherwise (new only if no transactional or otherwise pre-bound Session exists).
     *
     * @return the Session to use (never <code>null</code>)
     * @see SessionFactoryUtils#getSession
     * @see SessionFactoryUtils#getNewSession
     * @see #setAlwaysUseNewSession
     */
    protected Session getSession() {
        if (isAlwaysUseNewSession()) {
            return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor());
        } else if (SessionFactoryUtils.hasTransactionalSession(getSessionFactory())) {
            return SessionFactoryUtils.getSession(getSessionFactory(), false);
        } else {
            try {
                return getSessionFactory().getCurrentSession();
            } catch (HibernateException ex) {
                throw new DataAccessResourceFailureException("Could not obtain current Hibernate Session", ex);
            }
        }
    }

    public void initialize(Object proxy) throws DataAccessException {
        try {
            Hibernate.initialize(proxy);
        } catch (HibernateException ex) {
            throw SessionFactoryUtils.convertHibernateAccessException(ex);
        }
    }

    public Filter enableFilter(String filterName) throws IllegalStateException {
        Session session = SessionFactoryUtils.getSession(getSessionFactory(), false);
        Filter filter = session.getEnabledFilter(filterName);
        if (filter == null) {
            filter = session.enableFilter(filterName);
        }
        return filter;
    }

    /**
     * Performs a Search.
     *
     * @param query
     * @param searchClass
     * @param <T>
     * @return
     */
    public <T> List<T> search(final org.apache.lucene.search.Query query, final Class<T> searchClass) {
        return (List<T>) executeWithNativeSession(new HibernateSearchCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                FullTextSession fullTextSession = Search.getFullTextSession(session);
                org.hibernate.Query hibernateQuery = fullTextSession.createFullTextQuery(query, searchClass);
                return hibernateQuery.list();
            }
        });

    }

    /**
     * Performs a Search.
     *
     * @param query
     * @param searchClass
     * @param <T>
     * @return
     */
    public <T> List<T> search(final org.apache.lucene.search.Query query, final Class... searchClass) {
        return (List<T>) executeWithNativeSession(new HibernateSearchCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                FullTextSession fullTextSession = Search.getFullTextSession(session);
                org.hibernate.Query hibernateQuery = fullTextSession.createFullTextQuery(query, searchClass);
                return hibernateQuery.list();
            }
        });

    }

    public <T> List<T> search(final String searchText, final List<FilterConfig> filters, final Class[] classes,
            final String... fields) {

        return (List<T>) executeWithNativeSession(new HibernateSearchCallback() {

            @Override
            public Object doInHibernate(Session session) throws HibernateException {
                MultiFieldQueryParser parser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
                try {
                    org.apache.lucene.search.Query q = parser.parse(searchText);
                    FullTextSession s = Search.getFullTextSession(session);

                    FullTextQuery ftq = s.createFullTextQuery(q, classes);
                    for (FilterConfig fc : filters) {
                        FullTextFilter filter = ftq.enableFullTextFilter(fc.getFilterName());

                        for (Map.Entry<String, Object> entry : fc.getParameters().entrySet()) {
                            filter.setParameter(entry.getKey(), entry.getValue());
                        }
                    }
                    return ftq.list();

                } catch (ParseException e) {
                    logger.error(e.getMessage(), e);
                    throw new HibernateException(e);
                }
            }
        });
    }

    /**
     * Performs a Search with a default analyzer of {@link org.apache.lucene.analysis.standard.StandardAnalyzer}.
     * 
     * @param searchText
     * @param searchClass
     * @param fields
     * @param <T>
     * @return
     */
    public <T> List<T> search(final String searchText, final Class<T> searchClass, final String... fields) {
        return search(searchText, searchClass, new StandardAnalyzer(), fields);
    }

    /**
     * Performs a Search with a default analyzer of {@link org.apache.lucene.analysis.standard.StandardAnalyzer}.
     *
     * @param searchText
     * @param searchClass
     * @param fields
     * @param <T>
     * @return
     */
    public <T> List<T> search(final String searchText, final Class[] searchClass, final String... fields) {
        return search(searchText, searchClass, new StandardAnalyzer(), fields);
    }

    /**
     * 
     * @param searchText
     * @param searchClass
     * @param analyzer
     * @param fields
     * @param <T>
     * @return
     */
    public <T> List<T> search(final String searchText, final Class<T> searchClass, final Analyzer analyzer,
            final String... fields) {
        QueryParser parser = new MultiFieldQueryParser(fields, analyzer);
        org.apache.lucene.search.Query query;
        try {
            query = parser.parse(searchText);
        } catch (ParseException e) {
            throw new RuntimeException("Error parsing search text: " + searchText, e);
        }
        return search(query, searchClass);
    }

    /**
     *
     * @param searchText
     * @param searchClass
     * @param analyzer
     * @param fields
     * @param <T>
     * @return
     */
    public <T> List<T> search(final String searchText, final Class[] searchClass, final Analyzer analyzer,
            final String... fields) {
        QueryParser parser = new MultiFieldQueryParser(fields, analyzer);
        org.apache.lucene.search.Query query;
        try {
            query = parser.parse(searchText);
        } catch (ParseException e) {
            throw new RuntimeException("Error parsing search text: " + searchText, e);
        }
        return search(query, searchClass);
    }

    /**
     * Invocation handler that suppresses close calls on Hibernate Sessions.
     * Also prepares returned Query and Criteria objects.
     * @see org.hibernate.Session#close
     */
    private class CloseSuppressingInvocationHandler implements InvocationHandler {

        private final Session target;

        public CloseSuppressingInvocationHandler(Session target) {
            this.target = target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // Invocation on Session interface coming in...

            if (method.getName().equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
            } else if (method.getName().equals("hashCode")) {
                // Use hashCode of Session proxy.
                return new Integer(System.identityHashCode(proxy));
            } else if (method.getName().equals("close")) {
                // Handle close method: suppress, not valid.
                return null;
            }

            // Invoke method on target Session.
            try {
                Object retVal = method.invoke(this.target, args);

                //            // If return value is a Query or Criteria, apply transaction timeout.
                //            // Applies to createQuery, getNamedQuery, createCriteria.
                //            if (retVal instanceof Query) {
                //               prepareQuery(((Query) retVal));
                //            }
                //            if (retVal instanceof Criteria) {
                //               prepareCriteria(((Criteria) retVal));
                //            }

                return retVal;
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }
}