org.jdal.dao.jpa.JpaDao.java Source code

Java tutorial

Introduction

Here is the source code for org.jdal.dao.jpa.JpaDao.java

Source

/*
 * Copyright 2009-2011 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 org.jdal.dao.jpa;

import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdal.dao.DaoSupport;
import org.jdal.dao.Filter;
import org.jdal.dao.Page;
import org.jdal.dao.PageableDataSource;
import org.jdal.dao.jpa.query.EntityTypeQueryFinder;
import org.jdal.dao.jpa.query.QueryFinder;
import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.dao.InvalidDataAccessApiUsageException;

/**
 * Dao implementation for JPA
 * 
 * @author Jose Luis Martin
 * @see org.jdal.dao.Dao
 * @since 1.1
 */
public class JpaDao<T, PK extends Serializable> extends DaoSupport<T, PK> {

    private static final int DEFAULT_DEPTH = 2;
    private static final Log log = LogFactory.getLog(JpaDao.class);
    @PersistenceContext
    private EntityManager em;
    private Class<T> entityClass;
    private Map<String, JpaCriteriaBuilder<?>> criteriaBuilderMap = Collections
            .synchronizedMap(new HashMap<String, JpaCriteriaBuilder<?>>());

    private QueryFinder queryFinder;
    private boolean onDeleteSetNull = true;

    /**
     * Default Ctor, When using it, you need to set entityClass 
     */
    public JpaDao() {

    }

    /**
     * Create a new JpaDao for entity class
     * @param entityClass class to map.
     */
    public JpaDao(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public <K> Page<K> getPage(Page<K> page) {

        // try named query first
        TypedQuery<K> query = getNamedQuery(page);

        if (query == null) // get query from criteria
            query = getCriteriaQuery(page);

        // add range
        query.setMaxResults(page.getPageSize());
        query.setFirstResult(page.getStartIndex());

        page.setData(query.getResultList());
        page.setPageableDataSource(((PageableDataSource<K>) this));
        return page;
    }

    /**
     * Build CriteriaQuery using declared JpaCriteriaBuilder in filterMap
     * @param page
     * @return a CriteriaQuery from filter
     */
    @SuppressWarnings("unchecked")
    private <K> CriteriaQuery<K> getCriteria(Page<K> page) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<K> c = (CriteriaQuery<K>) cb.createQuery();
        Filter filter = null;
        if (page.getFilter() instanceof Filter) {
            filter = (Filter) page.getFilter();
            JpaCriteriaBuilder<K> jcb = (JpaCriteriaBuilder<K>) criteriaBuilderMap.get(filter.getFilterName());
            if (jcb != null) {
                if (log.isDebugEnabled())
                    log.debug("Found JpaCriteriaBuilder for filter: " + filter.getFilterName());
                // build criteria
                c = jcb.build(c, cb, filter);
            } else {
                log.error("No CriteriaBuilder found for filter name [" + filter.getFilterName() + "]");
                c.from(entityClass);
            }
        } else {
            c.select((Selection<? extends K>) c.from(getEntityClass()));
        }

        return c;
    }

    /**
     * Create a TypedQuery from a request page
     * @param page request page
     * @return new TypedQuery
     */
    @SuppressWarnings("unchecked")
    private <K> TypedQuery<K> getCriteriaQuery(Page<K> page) {
        CriteriaQuery<K> criteria = getCriteria(page);

        CriteriaQuery<Long> countCriteria = (CriteriaQuery<Long>) getCriteria(page);
        CriteriaBuilder cb = em.getCriteriaBuilder();

        Root<?> root = countCriteria.getRoots().iterator().next();
        countCriteria.select(cb.count(root));

        page.setCount((em.createQuery(countCriteria).getSingleResult()).intValue());

        criteria.orderBy(getOrder(page, criteria));

        // Add default select to entity class if none was set.
        if (criteria.getSelection() == null) {
            criteria.select((Selection<? extends K>) root);
        }

        return em.createQuery(criteria);
    }

    /**
     * {@inheritDoc}
     */
    public List<Serializable> getKeys(Page<T> page) {
        Filter filter = null;
        TypedQuery<Serializable> query = null;
        SingularAttribute<? super T, ?> id = getIdAttribute();
        // try named query
        if (page.getFilter() instanceof Filter) {
            filter = (Filter) page.getFilter();
            String queryString = getQueryString(filter.getFilterName());
            if (queryString != null) {
                String keyQuery = JpaUtils.getKeyQuery(queryString, id.getName());
                // add Order
                if (page.getSortName() != null)
                    keyQuery = JpaUtils.addOrder(keyQuery, page.getSortName(), page.getOrder() == Page.Order.ASC);

                query = em.createQuery(keyQuery, Serializable.class);
                applyFilter(query, filter);
            } else {
                query = getKeyCriteriaQuery(id, page);
            }
        } else {
            query = getKeyCriteriaQuery(id, page);
        }

        query.setFirstResult(page.getStartIndex());
        query.setMaxResults(page.getPageSize());

        return query.getResultList();
    }

    /**
     * Gets CriteriaQuery for Page Keys
     * @param page
     * @return CriteriaQuery 
     */
    @SuppressWarnings("unchecked")
    private TypedQuery<Serializable> getKeyCriteriaQuery(SingularAttribute<? super T, ?> id, Page<T> page) {
        CriteriaQuery<Serializable> keyCriteria = (CriteriaQuery<Serializable>) getCriteria(page);
        Root<T> keyRoot = JpaUtils.findRoot(keyCriteria, getEntityClass());
        keyCriteria.select(keyRoot.<Serializable>get(id.getName()));

        return em.createQuery(keyCriteria);
    }

    /**
     * Gets de id attribute from metamodel
     * @return PK SingularAttribute
     */
    private SingularAttribute<? super T, ?> getIdAttribute() {
        return getIdAttribute(getEntityClass());
    }

    /**
     * @param class1
     * @return
     */
    private <K> SingularAttribute<? super K, ?> getIdAttribute(Class<K> clazz) {
        Type<?> type = em.getMetamodel().entity(clazz).getIdType();
        EntityType<K> entity = em.getMetamodel().entity(clazz);
        SingularAttribute<? super K, ?> id = entity.getId(type.getJavaType());
        return id;
    }

    /**
     * Get JPA Order from a page order for a CriteriaQuery
     * @param page request page
     * @param criteria CriteriaQuery to apply Order on.
     * @return new Order
     */
    private Order getOrder(Page<?> page, CriteriaQuery<?> criteria) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        Root<T> root = JpaUtils.findRoot(criteria, getEntityClass());
        String propertyPath = page.getSortName();

        if (log.isDebugEnabled())
            log.debug("Setting order as: " + propertyPath);

        Path<?> path = JpaUtils.getPath(root, propertyPath);

        return page.getOrder() == Page.Order.ASC ? cb.asc(path) : cb.desc(path);
    }

    /**
     * Gets a NamedQuery from page, setup order, params and page result count.
     * @param page request page
     * @return a TypedQuery from a NamedQuery
     */
    @SuppressWarnings("unchecked")
    protected <K> TypedQuery<K> getNamedQuery(Page<K> page) {
        Filter filter = null;
        TypedQuery<K> query = null;

        if (page.getFilter() instanceof Filter) {
            filter = (Filter) page.getFilter();
            String queryString = getQueryString(filter.getFilterName());
            if (queryString != null) {
                String countQueryString = JpaUtils.createCountQueryString(queryString);
                TypedQuery<Long> countQuery = em.createQuery(countQueryString, Long.class);
                applyFilter(countQuery, filter);
                page.setCount(countQuery.getSingleResult().intValue());
                // add Order
                if (page.getSortName() != null)
                    queryString = JpaUtils.addOrder(queryString, page.getSortName(),
                            page.getOrder() == Page.Order.ASC);

                query = (TypedQuery<K>) em.createQuery(queryString);
                applyFilter(query, filter);
            }
        }

        return query;
    }

    /**
     * Apply filter to parametriced Query
     * @param query the query to apply filter on
     * @param filter Filter to apply
     */
    private void applyFilter(Query query, Filter filter) {
        Map<String, Object> parameterMap = filter.getParameterMap();
        for (Parameter<?> p : query.getParameters()) {
            if (parameterMap.containsKey(p.getName())) {
                query.setParameter(p.getName(), parameterMap.get(p.getName()));
            } else {
                throw new InvalidDataAccessApiUsageException("Parameter " + p.getName() + "was not found in filter "
                        + filter.getFilterName() + ". Review NamedQuery or Filter.");
            }
        }
    }

    /**
     * Gets query string fro named query using configured QueryFinder, if it's null
     * use EntityTypeQueryFinder as defaults (looks for anntations on entity class).
     * @param name query name
     * @return query string.
     */
    protected String getQueryString(String name) {
        if (queryFinder == null)
            queryFinder = new EntityTypeQueryFinder(em.getMetamodel().entity(getEntityClass()));

        return queryFinder.find(name);
    }

    /**
     * {@inheritDoc}
     */
    public List<T> findByNamedQuery(String queryName, Map<String, Object> queryParams) {
        TypedQuery<T> query = em.createNamedQuery(queryName, getEntityClass());
        if (queryParams != null) {
            for (Map.Entry<String, ?> entry : queryParams.entrySet()) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }
        return query.getResultList();
    }

    /**
     * {@inheritDoc}
     */
    public T get(PK id) {
        return em.find(getEntityClass(), id);
    }

    /**
     * {@inheritDoc}
     */
    public List<T> getAll() {
        CriteriaQuery<T> q = em.getCriteriaBuilder().createQuery(getEntityClass());
        q.from(getEntityClass());

        return em.createQuery(q).getResultList();
    }

    /**
     * {@inheritDoc}
     */
    public void delete(T entity) {
        if (!em.contains(entity))
            entity = em.merge(entity);

        if (onDeleteSetNull)
            nullReferences(entity);

        em.remove(entity);
    }

    /**
     * {@inheritDoc}
     */
    public void deleteById(PK id) {
        delete(get(id));

    }

    /**
     * Null References on one to many and one to one associations.
     * Will only work if association has annotated with a mappedBy attribute.
     * 
     * @param entity entity
     */
    private void nullReferences(T entity) {
        EntityType<T> type = em.getMetamodel().entity(getEntityClass());

        if (log.isDebugEnabled())
            log.debug("Null references on entity " + type.getName());

        for (Attribute<?, ?> a : type.getAttributes()) {
            if (PersistentAttributeType.ONE_TO_MANY == a.getPersistentAttributeType()
                    || PersistentAttributeType.ONE_TO_ONE == a.getPersistentAttributeType()) {
                Object association = PropertyAccessorFactory.forDirectFieldAccess(entity)
                        .getPropertyValue(a.getName());
                if (association != null) {
                    EntityType<?> associationType = null;
                    if (a.isCollection()) {
                        associationType = em.getMetamodel()
                                .entity(((PluralAttribute<?, ?, ?>) a).getBindableJavaType());
                    } else {
                        associationType = em.getMetamodel().entity(a.getJavaType());

                    }

                    String mappedBy = JpaUtils.getMappedBy(a);
                    if (mappedBy != null) {
                        Attribute<?, ?> aa = associationType.getAttribute(mappedBy);
                        if (PersistentAttributeType.MANY_TO_ONE == aa.getPersistentAttributeType()) {
                            if (log.isDebugEnabled()) {
                                log.debug("Null ManyToOne reference on " + associationType.getName() + "."
                                        + aa.getName());
                            }
                            for (Object o : (Collection<?>) association) {
                                PropertyAccessorFactory.forDirectFieldAccess(o).setPropertyValue(aa.getName(),
                                        null);
                            }
                        } else if (PersistentAttributeType.ONE_TO_ONE == aa.getPersistentAttributeType()) {
                            if (log.isDebugEnabled()) {
                                log.debug("Null OneToOne reference on " + associationType.getName() + "."
                                        + aa.getName());
                            }
                            PropertyAccessorFactory.forDirectFieldAccess(association).setPropertyValue(aa.getName(),
                                    null);
                        }
                    }
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public boolean exists(PK id) {
        return id != null && get(id) != null;
    }

    /**
     * {@inheritDoc}
     */
    public T initialize(T entity) {
        initialize(entity, DEFAULT_DEPTH);
        return entity;
    }

    /**
     * {@inheritDoc}
     */
    public T initialize(T entity, int depth) {
        JpaUtils.initialize(em, entity, depth);
        return entity;
    }

    /**
     * {@inheritDoc}
     */
    public T save(T entity) {
        T persistentEntity;

        if (isNew(entity)) {
            em.persist(entity);
            persistentEntity = entity;
        } else {
            persistentEntity = em.merge(entity);
        }

        return persistentEntity;

    }

    /**
     * Test if entity is New
     * @param entity
     * @return true if entity is new, ie not detached
     */
    @SuppressWarnings("unchecked")
    protected boolean isNew(T entity) {
        SingularAttribute<?, ?> id = getIdAttribute(entity.getClass());
        // try field
        PropertyAccessor pa = PropertyAccessorFactory.forDirectFieldAccess(entity);
        PK key = (PK) pa.getPropertyValue(id.getName());
        if (key == null)
            key = (PK) PropertyAccessorFactory.forBeanPropertyAccess(entity).getPropertyValue(id.getName());

        return key == null || !exists(key, entity.getClass());
    }

    /**
     * @param key
     * @param class1
     * @return
     */
    private boolean exists(PK key, Class<? extends Object> clazz) {
        return get(key, clazz) != null;
    }

    /**
     * @return entity class
     */
    public Class<T> getEntityClass() {
        return entityClass;
    }

    /**
     * @return the criteriaBuilderMap
     */
    public Map<String, JpaCriteriaBuilder<?>> getCriteriaBuilderMap() {
        return criteriaBuilderMap;
    }

    /**
     * @param criteriaBuilderMap the criteriaBuilderMap to set
     */
    public void setCriteriaBuilderMap(Map<String, JpaCriteriaBuilder<T>> criteriaBuilderMap) {
        this.criteriaBuilderMap.clear();
        this.criteriaBuilderMap.putAll(criteriaBuilderMap);
    }

    /**
     * @return the queryFinder
     */
    public QueryFinder getQueryFinder() {
        return queryFinder;
    }

    /**
     * @param queryFinder the queryFinder to set
     */
    public void setQueryFinder(QueryFinder queryFinder) {
        this.queryFinder = queryFinder;
    }

    /**
     * {@inheritDoc}
     */
    public <E> E get(PK id, Class<E> clazz) {
        return em.find(clazz, id);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> getAll(Class<E> clazz) {
        CriteriaQuery<E> q = em.getCriteriaBuilder().createQuery(clazz);
        q.from(clazz);

        return em.createQuery(q).getResultList();
    }

    /**
     * @return the em
     */
    public EntityManager getEntityManager() {
        return em;
    }

    /**
     * @param em the em to set
     */
    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

    /**
     * @param entityClass the entityClass to set
     */
    public void setEntityClass(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    /**
     * @return the onDeleteSetNull
     */
    public boolean isOnDeleteSetNull() {
        return onDeleteSetNull;
    }

    /**
     * @param onDeleteSetNull the onDeleteSetNull to set
     */
    public void setOnDeleteSetNull(boolean onDeleteSetNull) {
        this.onDeleteSetNull = onDeleteSetNull;
    }

}