/* * Copyright 2009-2015 Jose Luis Martin. * * 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 org.jdal.dao.hibernate; import java.io.Serializable; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.Criteria; import org.hibernate.HibernateException; import org.hibernate.LockOptions; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Example; import org.hibernate.criterion.Order; import org.hibernate.criterion.Projections; import org.hibernate.impl.CriteriaImpl; import org.hibernate.metadata.ClassMetadata; import org.hibernate.transform.ResultTransformer; import org.hibernate.type.Type; import org.jdal.beans.PropertyUtils; import org.jdal.dao.DaoSupport; import org.jdal.dao.Filter; import org.jdal.dao.Page; import org.jdal.hibernate.HibernateUtils; import org.springframework.beans.PropertyAccessor; import org.springframework.orm.ObjectRetrievalFailureException; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.util.ClassUtils; /** * Hibernate generic DAO implementation. Support pagination of results and filters * using the {@link #getPage(Page)} method. * * @author Jose Luis Martin * @see org.jdal.dao.Dao * @since 1.0 */ public class HibernateDao<T, PK extends Serializable> extends DaoSupport<T, PK>{ private static final Log log = LogFactory.getLog(HibernateDao.class); private Class<T> entityClass; private boolean cachePageQueries = false; private HibernateTemplate hibernateTemplate; /** * Synchronized map with CriteriaBuilders by name */ private Map<String, CriteriaBuilder> criteriaBuilderMap = Collections.synchronizedMap(new HashMap<String, CriteriaBuilder>()); public HibernateDao() { } /** * @param persistentClass */ public HibernateDao(Class<T> persistentClass) { this.entityClass = persistentClass; } /** * Get Page, apply filter if any. * If Filter is a entity model, use Example to create a criteria. * else enable filter by name on session. * @param page with page definitions * @return page of results */ @SuppressWarnings({ "unchecked", "rawtypes" }) public <K> Page<K> getPage(Page<K> page) { List data = null; // try named query Query query = getQuery(page); if (query != null) { data = query.list(); } else { // try filter, example and criteria builders Criteria criteria = getCriteria(page); ResultTransformer rt = ((CriteriaImpl) criteria).getResultTransformer(); criteria.setProjection(Projections.rowCount()); page.setCount(((Long) criteria.uniqueResult()).intValue()); // reset criteria criteria.setProjection(null); criteria.setResultTransformer(rt); // set start index and page size criteria.setFirstResult(page.getStartIndex()) .setMaxResults(page.getPageSize()); applyOrder(page, criteria); // run it criteria.setCacheable(cachePageQueries); data = criteria.list(); } page.setData(data); return page; } /** * Get Hibernate named Query and configure with filter from page. * Set the result count on page also. * @param page page * @return Hibernate named Query. */ private Query getQuery(Page<?> page) { Object filter = page.getFilter(); try { if (filter instanceof Filter) { Filter f = (Filter) filter; Query query = getSession().getNamedQuery(f.getFilterName()); Query countQuery = getSession().createQuery(query.getQueryString().replaceFirst("select", "count")); query.setProperties(f.getParameterMap()); query.setMaxResults(page.getPageSize()); query.setFirstResult(page.getStartIndex()); page.setCount((Integer) countQuery.uniqueResult()); return query; } } catch (HibernateException e) {} return null; } /** * Create a Criteria from filter. If filter is a entity class instance, * return a Criteria with a Example Criterion applied. * If not try four options in order: * * 1. if there are a filter with name filter.getName() enable it and return criteria * 2. if there are a criteria builder with name filter.getName() use it to create Critera. * 4. if there are a method named createCritera + filter.getName() invoke it to create Criteria * 5. Return a Criteria for this entity class without Criterion applied. * * @param filter the filter * @return a new Criteria */ protected Criteria getCriteria(Page<?> page) { Criteria executableCriteria = getSession().createCriteria( getEntityClass()); Object filter = page.getFilter(); // apply filter, if any if (filter != null) { if (ClassUtils.isAssignable(getEntityClass(), filter.getClass())) { // try a findByExample... executableCriteria.add(Example.create(getEntityClass())); } else if (filter instanceof Filter) { Filter f = (Filter) filter; if (!enableFilter(f)) { if (log.isDebugEnabled()) log.debug("No hibernate filter found with name: " + f.getFilterName() + ", try criteria builder."); // if no filter, try criteria builder if (criteriaBuilderMap.containsKey(f.getFilterName())) { CriteriaBuilder cb = criteriaBuilderMap.get(f.getFilterName()); if(log.isDebugEnabled()) log.debug("Found criteria builder with name: " + f.getFilterName() + " - criteria builder class: " + cb.getClass().getSimpleName()); executableCriteria = cb.build(executableCriteria, f); } // if no criteria builder try subclass method else if (ClassUtils.hasMethod(getClass(), "createCriteria" + f.getFilterName(), new Class[] {Criteria.class})) { Method method = ClassUtils.getMethodIfAvailable( getClass(), "createCriteria" + f.getFilterName(), new Class[] {Criteria.class}); if (method != null) { try { executableCriteria = (Criteria) method.invoke(this, executableCriteria); } catch (Exception e) { log.error(e); } } } } } else { log.warn("Cannot manage filter of type: " + filter.getClass()); } } return executableCriteria; } /** * Enable predefined filter in current session * @param f Filter with filter name and parameters * @return true if hibernate filter exists */ public boolean enableFilter(Filter f) { if (getSessionFactory().getDefinedFilterNames().contains(f.getFilterName())) { org.hibernate.Filter hf = getSession().enableFilter(f.getFilterName()); Map<String, Object> parameterMap = f.getParameterMap(); for (String key :parameterMap.keySet()) { hf.setParameter(key, parameterMap.get(key)); } return true; } return false; } /** * @return ClassMetadata from entityClass */ private ClassMetadata getClassMetadata() { return getClassMetadata(getEntityClass()); } /** * return ClassMetadata from Class * @param clazz the class * @return the ClassMetadata */ private ClassMetadata getClassMetadata(Class<?> clazz) { return getHibernateTemplate().getSessionFactory(). getClassMetadata(clazz); } /** * Apply Order to Criteria * @param page the page * @param criteria the criteria */ protected void applyOrder(Page<?> page, Criteria criteria) { Order order = createOrder(criteria, page.getSortName(), Page.Order.ASC.equals(page.getOrder())); if (order != null) criteria.addOrder(order); } /** * Create Order from criteria and property path * @param criteria the hibernate criteria to apply order on * @param propertyPath the property path * @return Order */ protected Order createOrder(Criteria criteria, String propertyPath, boolean ascending) { Order order = null; if (propertyPath != null) { String sortProperty = PropertyUtils.getPropertyName(propertyPath); try { if (PropertyUtils.isNested(propertyPath)) { String alias = PropertyUtils.getPropertyName(PropertyUtils.getPath(propertyPath)); // Need to create alias? // String alias = HibernateUtils.findAliasForPropertyPath(criteria, propertyPath); HibernateUtils.createAlias(criteria, PropertyUtils.getPath(propertyPath)); sortProperty = alias + PropertyUtils.PROPERTY_SEPARATOR + sortProperty; } else { // test if property is an entity class Type sortType = getClassMetadata().getPropertyType(propertyPath); if (sortType.isEntityType()) { // is entity, look for 'name' property String[] propertyNames = getClassMetadata(sortType.getReturnedClass()).getPropertyNames(); for (String name : propertyNames) { if ("name".equals(name)) { log.info("Found property name on persistent class: " + sortType.getName()); String newPath = propertyPath + PropertyAccessor.NESTED_PROPERTY_SEPARATOR + "name"; return createOrder(criteria, newPath, ascending); } } } } if (log.isDebugEnabled()) log.debug("Setting order as: " + sortProperty); order = ascending ? Order.asc(sortProperty) : Order.desc(sortProperty); } catch(HibernateException he) { log.error("Cannot to create Order for property: " + sortProperty + " for " + getEntityClass().getSimpleName(), he); } } else { // add default order by id ClassMetadata metadata = getClassMetadata(); if (metadata != null) order = Order.asc(metadata.getIdentifierPropertyName()); } return order; } /** * @return the entityClass */ public Class<T> getEntityClass() { return entityClass; } /** * @param entityClass the entityClass to set */ public void setEntityClass(Class<T> entityClass) { this.entityClass = entityClass; } /** * Delete a entity from db * @param entity */ public void delete(T entity) { getHibernateTemplate().delete(entity); } public T save(T entity) { getHibernateTemplate().saveOrUpdate(entity); return entity; } @SuppressWarnings("unchecked") public List<Serializable> getKeys(Page<T> page) { Criteria criteria = getCriteria(page); return criteria.setProjection(Projections.id()).list(); } public void deleteById(PK id) { getSession().delete(get(id)); } public T initialize(T entity) { getSession().buildLockRequest(LockOptions.NONE).lock(entity); HibernateUtils.initialize(getSessionFactory(), entity); return entity; } public T initialize(T entity, int depth) { getSession().buildLockRequest(LockOptions.NONE).lock(entity); HibernateUtils.initialize(getSessionFactory(), entity, depth); return entity; } /** * @return the criteriaBuilderMap */ public Map<String, CriteriaBuilder> getCriteriaBuilderMap() { return criteriaBuilderMap; } /** * @param criteriaBuilderMap the criteriaBuilderMap to set */ public void setCriteriaBuilderMap( Map<String, CriteriaBuilder> criteriaBuilderMap) { this.criteriaBuilderMap.clear(); this.criteriaBuilderMap.putAll(criteriaBuilderMap); } /** * {@inheritDoc} */ public List<T> getAll() { return new ArrayList<T>(getHibernateTemplate().loadAll(this.entityClass)); } public List<T> getAllDistinct() { Collection<T> result = new LinkedHashSet<T>(getAll()); return new ArrayList<T>(result); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public List<T> findByNamedQuery( String queryName, Map<String, Object> queryParams) { String []params = new String[queryParams.size()]; Object []values = new Object[queryParams.size()]; int index = 0; Iterator<String> i = queryParams.keySet().iterator(); while (i.hasNext()) { String key = i.next(); params[index] = key; values[index++] = queryParams.get(key); } return (List<T>) getHibernateTemplate().findByNamedQueryAndNamedParam( queryName, params, values); } /** * {@inheritDoc} */ public T get(PK id) { T entity = (T) getHibernateTemplate().get(this.entityClass, id); if (entity == null) { log.warn("'" + this.entityClass.getSimpleName() + "' object with id '" + id + "' not found..."); throw new ObjectRetrievalFailureException(this.entityClass, id); } return entity; } /** * {@inheritDoc} */ public boolean exists(PK id) { return getHibernateTemplate().get(this.entityClass, id) != null; } /** * Count rows * @return number of rows */ public int count() { return ((Long) getSession().createCriteria(entityClass) .setProjection(Projections.rowCount()) .uniqueResult()).intValue(); } /** * @return the cachePageQueries */ public boolean isCachePageQueries() { return cachePageQueries; } /** * @param cachePageQueries the cachePageQueries to set */ public void setCachePageQueries(boolean cachePageQueries) { this.cachePageQueries = cachePageQueries; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public <E> E get(PK id, Class<E> clazz) { return (E) getSession().get(clazz, id); } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public <E> List<E> getAll(Class<E> clazz) { return getSession().createCriteria(clazz).list(); } public HibernateTemplate getHibernateTemplate() { return hibernateTemplate; } public void setHibernateTemplate(HibernateTemplate hibernateTemplate) { this.hibernateTemplate = hibernateTemplate; } public final void setSessionFactory(SessionFactory sessionFactory) { if (this.hibernateTemplate == null || sessionFactory != this.hibernateTemplate.getSessionFactory()) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } } /** * Return the Hibernate SessionFactory */ public final SessionFactory getSessionFactory() { return (this.hibernateTemplate != null ? this.hibernateTemplate.getSessionFactory() : null); } /** * Return current hibernate Session from Hibernate template * @return curren hibernate Session */ public Session getSession() { return hibernateTemplate.getSessionFactory().getCurrentSession(); } }