nl.strohalm.cyclos.utils.hibernate.HibernateQueryHandler.java Source code

Java tutorial

Introduction

Here is the source code for nl.strohalm.cyclos.utils.hibernate.HibernateQueryHandler.java

Source

/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
    
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
    
Cyclos 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 General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 */
package nl.strohalm.cyclos.utils.hibernate;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import nl.strohalm.cyclos.dao.FetchDAO;
import nl.strohalm.cyclos.entities.Entity;
import nl.strohalm.cyclos.entities.EntityReference;
import nl.strohalm.cyclos.entities.Relationship;
import nl.strohalm.cyclos.entities.exceptions.EntityNotFoundException;
import nl.strohalm.cyclos.utils.ClassHelper;
import nl.strohalm.cyclos.utils.EntityHelper;
import nl.strohalm.cyclos.utils.FetchingIteratorListImpl;
import nl.strohalm.cyclos.utils.IteratorListImpl;
import nl.strohalm.cyclos.utils.PropertyHelper;
import nl.strohalm.cyclos.utils.ScrollableResultsIterator;
import nl.strohalm.cyclos.utils.query.IteratorList;
import nl.strohalm.cyclos.utils.query.Page;
import nl.strohalm.cyclos.utils.query.PageImpl;
import nl.strohalm.cyclos.utils.query.PageParameters;
import nl.strohalm.cyclos.utils.query.QueryParameters.ResultType;

import org.apache.commons.lang.StringUtils;
import org.hibernate.EntityMode;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.ObjectNotFoundException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.proxy.HibernateProxy;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.type.CollectionType;
import org.hibernate.type.EntityType;
import org.hibernate.type.MapType;
import org.hibernate.type.Type;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * Handler for entity queries using Hibernate
 * @author luis
 */
public class HibernateQueryHandler {

    private static Pattern FIRST_ALIAS = Pattern.compile("^ *(from +[^ ]+|select(?: +distinct)?) +([^ ]+).*");
    private static Pattern LEFT_JOIN_FETCH = Pattern
            .compile("\\^left\\s+join\\s+fetch(\\s+[\\w\\.]+)?(\\s*[\\w]+)?");

    /**
     * Returns an HQL query without the fetch part
     */
    private static String stripFetch(String hql) {
        // This is done so we don't confuse the matcher, i.e.: from X x left join fetch x.a *left* join fetch ... -> that *left* could be an alias
        hql = hql.replaceAll("left join", "^left join");

        Matcher matcher = LEFT_JOIN_FETCH.matcher(hql);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String path = StringUtils.trimToEmpty(matcher.group(1));
            String alias = StringUtils.trimToEmpty(matcher.group(2));
            boolean nextIsLeft = "left".equalsIgnoreCase(alias);
            boolean safeToRemove = alias.isEmpty() || nextIsLeft || "where".equalsIgnoreCase(alias)
                    || "order".equalsIgnoreCase(alias) || "group".equalsIgnoreCase(alias);
            String replacement;
            if (safeToRemove) {
                // No alias - we can just remove the entire left join fetch
                replacement = nextIsLeft ? "" : " " + alias;
            } else {
                // Just remove the 'fetch'
                replacement = " left join " + path + " " + alias;
            }
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        return sb.toString().replaceAll("\\^left join", "left join");
    }

    /**
     * Return a count HQL. Should handle fetches and order by, removing them
     */
    private static String transformToCount(String hql) {
        hql = stripFetch(hql);
        final int fromIndex = hql.indexOf("from ");
        final int orderIndex = hql.indexOf("order by ");
        final StringBuilder sb = new StringBuilder();
        final boolean isDistinct = hql.indexOf(" distinct ") >= 0;
        final Matcher matcher = FIRST_ALIAS.matcher(hql);
        if (matcher.matches()) {
            final String firstAlias = matcher.group(2);
            sb.append("select count(").append(isDistinct ? " distinct " : "").append(firstAlias).append(".id) ");
        } else {
            sb.append("select count(*)");
        }
        if (orderIndex < 0) {
            // select a from A a where 1=1
            // -> select count(*) from A a where 1=1
            sb.append(hql.substring(fromIndex));
        } else {
            // select a, b from A a where 1=1 order by a.name
            // -> select count(*) from A a where 1=1
            sb.append(hql.substring(fromIndex, orderIndex));
        }
        return sb.toString();
    }

    private FetchDAO fetchDao;

    private HibernateTemplate hibernateTemplate;

    /**
     * Applies the result limits to the given query
     */
    public void applyPageParameters(final PageParameters pageParameters, final Query query) {
        Integer firstResult = pageParameters == null ? null : pageParameters.getFirstResult();
        if (firstResult != null && firstResult >= 0) {
            query.setFirstResult(firstResult);
        }
        Integer maxResults = pageParameters == null ? null : pageParameters.getMaxResults();
        if (maxResults != null && maxResults > 0) {
            query.setMaxResults(maxResults);
        }
    }

    /**
     * Apply the page parameters to an in-memory collection
     */
    public <E> List<E> applyResultParameters(final ResultType resultType, final PageParameters pageParameters,
            final Collection<E> records) {
        switch (resultType) {
        case LIST:
        case ITERATOR:
            final int maxResults = pageParameters == null ? Integer.MAX_VALUE : pageParameters.getMaxResults();
            List<E> list = new ArrayList<E>();
            if (maxResults > 0) {
                int i = 0;
                for (final E item : records) {
                    list.add(item);
                    i++;
                    if (i >= maxResults) {
                        break;
                    }
                }
            }
            if (resultType == ResultType.ITERATOR) {
                list = new IteratorListImpl<E>(list.iterator());
            }
            return list;
        case PAGE:
            final int currentPage = pageParameters == null ? 0 : pageParameters.getCurrentPage();
            final int pageSize = pageParameters == null ? 15 : pageParameters.getPageSize();
            final int firstIndex = currentPage * pageSize;
            final int lastIndex = firstIndex + pageSize;
            final List<E> pageElements = new ArrayList<E>(pageSize);
            int index = -1;
            for (final E payment : records) {
                index++;
                if (index < firstIndex) {
                    continue;
                }
                if (index > lastIndex) {
                    break;
                }
                pageElements.add(payment);
            }
            return new PageImpl<E>(pageParameters, records.size(), pageElements);
        default:
            throw new IllegalStateException(resultType + "?");
        }
    }

    /**
     * Copies the persistent properties from the source to the destination entity
     */
    public void copyProperties(final Entity source, final Entity dest) {
        if (source == null || dest == null) {
            return;
        }
        final ClassMetadata metaData = getClassMetaData(source);
        final Object[] values = metaData.getPropertyValues(source, EntityMode.POJO);
        // Skip the collections
        final Type[] types = metaData.getPropertyTypes();
        for (int i = 0; i < types.length; i++) {
            final Type type = types[i];
            if (type instanceof CollectionType) {
                values[i] = null;
            }
        }
        metaData.setPropertyValues(dest, values, EntityMode.POJO);
    }

    /**
     * Execute a query based on the parameters
     * @param <E> The entity type
     * @param resultType The desired result type
     * @param hql The HQL query
     * @param namedParameters The HQL named parameter values (normally a Map or a Bean)
     * @param pageParameters The page parameters. Affects all ResultTypes, by limiting the number of results
     * @param fetch The relationships to fetch, if any
     * @return A List of the expected entity type. It will be:
     * <ul>
     * <li>A {@link Page} when ResultType == PAGE</li>
     * <li>A {@link IteratorList} when ResultType == ITERATOR</li>
     * <li>A {@link List} when ResultType == LIST</li>
     * </ul>
     */
    public <E> List<E> executeQuery(final String cacheRegion, final ResultType resultType, final String hql,
            final Object namedParameters, final PageParameters pageParameters, final Relationship... fetch) {

        // Check the result type
        switch (resultType) {
        case LIST:
            return list(cacheRegion, hql, namedParameters, pageParameters, fetch);
        case PAGE:
            return page(cacheRegion, hql, namedParameters, pageParameters, fetch);
        case ITERATOR:
            // Iterators shouldn't use cache to avoid too many objects in memory
            return iterator(hql, namedParameters, pageParameters, fetch);
        default:
            throw new IllegalArgumentException("Unknown result type: " + resultType);
        }
    }

    /**
     * Returns the class meta data for the given entity
     */
    public ClassMetadata getClassMetaData(final Entity entity) {
        return hibernateTemplate.getSessionFactory().getClassMetadata(EntityHelper.getRealClass(entity));
    }

    public FetchDAO getFetchDao() {
        return fetchDao;
    }

    /**
     * Initialize an entity or collection
     */
    public Object initialize(final Object object) {
        if (object instanceof HibernateProxy) {
            // Reassociate the entity with the current session
            Entity entity = (Entity) object;
            entity = getHibernateTemplate().load(EntityHelper.getRealClass(entity), entity.getId());
            // Return the implementation associated with the proxy
            if (entity instanceof HibernateProxy) {
                final LazyInitializer lazyInitializer = ((HibernateProxy) entity).getHibernateLazyInitializer();
                lazyInitializer.initialize();
                return lazyInitializer.getImplementation();
            } else {
                return entity;
            }
        } else if (object instanceof PersistentCollection) {
            // Reassociate the collection with the current session
            return getHibernateTemplate().execute(new HibernateCallback<Object>() {
                @Override
                public Object doInHibernate(final Session session) throws HibernateException, SQLException {
                    final PersistentCollection persistentCollection = ((PersistentCollection) object);
                    Entity owner = (Entity) persistentCollection.getOwner();
                    final String role = persistentCollection.getRole();
                    if (owner == null || role == null) {
                        return persistentCollection;
                    }
                    // Retrieve the owner of this persistent collection, associated with the current session
                    owner = (Entity) session.get(EntityHelper.getRealClass(owner), owner.getId());
                    // Retrieve the collection through it's role (property name)
                    final String propertyName = PropertyHelper.lastProperty(role);
                    final Object currentCollection = PropertyHelper.get(owner, propertyName);
                    if (currentCollection instanceof PersistentCollection) {
                        Hibernate.initialize(currentCollection);
                    }
                    return currentCollection;
                }
            });
        }
        try {
            Hibernate.initialize(object);
        } catch (final ObjectNotFoundException e) {
            throw new EntityNotFoundException();
        }
        return object;
    }

    /**
     * Initializes a lazy property
     */
    public Object initializeProperty(final Object bean, final String relationshipName) {
        final String first = PropertyHelper.firstProperty(relationshipName);
        Object value = PropertyHelper.get(bean, first);
        value = initialize(value);
        PropertyHelper.set(bean, first, value);
        return value;
    }

    @SuppressWarnings("unchecked")
    public void resolveReferences(final Entity entity) {
        final ClassMetadata meta = getClassMetaData(entity);
        final String[] names = meta.getPropertyNames();
        final Type[] types = meta.getPropertyTypes();
        for (int i = 0; i < types.length; i++) {
            final Type type = types[i];
            final String name = names[i];
            if (type instanceof EntityType) {
                // Properties that are relationships to other entities
                Entity rel = PropertyHelper.get(entity, name);
                if (rel instanceof EntityReference) {
                    rel = getHibernateTemplate().load(EntityHelper.getRealClass(rel), rel.getId());
                    PropertyHelper.set(entity, name, rel);
                }
            } else if (type instanceof CollectionType && !(type instanceof MapType)) {
                // Properties that are collections of other entities
                final Collection<?> current = PropertyHelper.get(entity, name);
                if (current != null && !(current instanceof PersistentCollection)) {
                    // We must check that the collection is made of entities, since Hibernate supports collections os values
                    boolean isEntityCollection = true;
                    final Collection<Entity> resolved = ClassHelper.instantiate(current.getClass());
                    for (final Object object : current) {
                        if (object != null && !(object instanceof Entity)) {
                            isEntityCollection = false;
                            break;
                        }
                        Entity e = (Entity) object;
                        if (object instanceof EntityReference) {
                            e = getHibernateTemplate().load(EntityHelper.getRealClass(e), e.getId());
                        }
                        resolved.add(e);
                    }
                    if (isEntityCollection) {
                        PropertyHelper.set(entity, name, resolved);
                    }
                }
            }
        }
    }

    public void setFetchDao(final FetchDAO fetchDao) {
        this.fetchDao = fetchDao;
    }

    /**
     * Sets the query bind named parameters
     */
    public void setQueryParameters(final Query query, final Object parameters) {
        if (parameters != null) {
            if (parameters instanceof Map<?, ?>) {
                final Map<?, ?> map = (Map<?, ?>) parameters;
                final String[] paramNames = query.getNamedParameters();
                for (final String param : paramNames) {
                    final Object value = map.get(param);
                    if (value instanceof Collection<?>) {
                        final Collection<Object> values = new ArrayList<Object>(((Collection<?>) value).size());
                        for (final Object object : (Collection<?>) value) {
                            if (object instanceof EntityReference) {
                                values.add(fetchDao.fetch((Entity) object));
                            } else {
                                values.add(object);
                            }
                        }
                        query.setParameterList(param, values);
                    } else if (value instanceof EntityReference) {
                        query.setParameter(param, fetchDao.fetch((Entity) value));
                    } else {
                        query.setParameter(param, value);
                    }
                }
            } else {
                query.setProperties(parameters);
            }
        }
    }

    public void setSessionFactory(final SessionFactory sessionFactory) {
        hibernateTemplate = new HibernateTemplate(sessionFactory);
    }

    /**
     * Iterate the query with hibernate
     */
    public <E> Iterator<E> simpleIterator(final String hql, final Object namedParameters,
            final PageParameters pageParameters) {
        final Iterator<E> iterator = getHibernateTemplate().execute(new HibernateCallback<Iterator<E>>() {
            @Override
            public Iterator<E> doInHibernate(final Session session) throws HibernateException {
                // Iterators cannot have fetch on HQL
                String strippedHql = stripFetch(hql);
                final Query query = session.createQuery(strippedHql);
                applyPageParameters(pageParameters, query);
                setQueryParameters(query, namedParameters);
                return new ScrollableResultsIterator<E>(query, null);
            }
        });
        return iterator;
    }

    /**
     * List the query with hibernate
     */
    @SuppressWarnings("unchecked")
    public <E> List<E> simpleList(final String cacheRegion, final String hql, final Object namedParameters,
            final PageParameters pageParameters, final Relationship... fetch) {
        final List<E> list = getHibernateTemplate().execute(new HibernateCallback<List<E>>() {
            @Override
            public List<E> doInHibernate(final Session session) throws HibernateException {
                final Query query = session.createQuery(hql);
                setQueryParameters(query, namedParameters);
                applyPageParameters(pageParameters, query);
                if (cacheRegion != null) {
                    query.setCacheable(true);
                    query.setCacheRegion(cacheRegion);
                }
                return query.list();
            }
        });
        if (fetch != null && fetch.length > 0) {
            for (int i = 0; i < list.size(); i++) {
                final Entity entity = (Entity) list.get(i);
                list.set(i, (E) fetchDao.fetch(entity, fetch));
            }
        }
        return list;
    }

    private HibernateTemplate getHibernateTemplate() {
        return hibernateTemplate;
    }

    /**
     * Execute the query for ResultType == ITERATOR
     */
    private <E> List<E> iterator(final String hql, final Object namedParameters,
            final PageParameters pageParameters, final Relationship[] fetch) {
        final Iterator<E> iterator = simpleIterator(hql, namedParameters, pageParameters);
        return new FetchingIteratorListImpl<E>(iterator, fetchDao, fetch);
    }

    /**
     * Execute the query for ResultType == LIST
     * @param pageParameters
     */
    private <E> List<E> list(final String cacheRegion, final String hql, final Object namedParameters,
            final PageParameters pageParameters, final Relationship... fetch) {
        return simpleList(cacheRegion, hql, namedParameters, pageParameters, fetch);
    }

    /**
     * Execute the query for ResultType == PAGE
     */
    private <E> List<E> page(final String cacheRegion, final String hql, final Object namedParameters,
            final PageParameters pageParameters, final Relationship[] fetch) {

        // Count all records
        final Integer totalCount = getHibernateTemplate().execute(new HibernateCallback<Integer>() {
            @Override
            public Integer doInHibernate(final Session session) throws HibernateException {
                final Query query = session.createQuery(transformToCount(hql.toString()));
                setQueryParameters(query, namedParameters);
                setCacheRegion(query, cacheRegion);
                return (Integer) query.uniqueResult();
            }
        });

        // Get only the page records
        List<E> list;
        if (pageParameters.getMaxResults() == 0) {
            // Max results == 0 means only to count
            list = Collections.emptyList();
        } else {
            list = simpleList(cacheRegion, hql, namedParameters, pageParameters, fetch);
        }

        // Create a page instance
        return new PageImpl<E>(pageParameters, totalCount, list);
    }

    private void setCacheRegion(final Query query, final String cacheRegion) {
        if (cacheRegion != null) {
            query.setCacheable(true);
            query.setCacheRegion(cacheRegion);
        }
    }

}