package jpasearch.repository;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.Serializable;
import java.util.List;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import jpasearch.domain.Identifiable;
import jpasearch.repository.query.ResultParameters;
import jpasearch.repository.query.SearchParameters;
import jpasearch.repository.query.builder.SearchBuilder;
import jpasearch.repository.util.BySelectorUtil;
import jpasearch.repository.util.JpaUtil;
import jpasearch.repository.util.MetamodelUtil;
import jpasearch.repository.util.OrderByUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JPA 2 Generic DAO with find by example/range/pattern and CRUD support.
*/
public abstract class JpaGenericRepository<E extends Identifiable<PK>, PK extends Serializable> implements GenericRepository<E, PK> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private BySelectorUtil bySelectorUtil;
private OrderByUtil orderByUtil;
private JpaUtil jpaUtil;
private MetamodelUtil metamodelUtil;
private EntityManager entityManager;
private final Class<E> type;
protected String cacheRegion;
/**
* This constructor needs the real type of the generic type E so it can be
* passed to the {@link EntityManager}.
*/
public JpaGenericRepository(Class<E> type) {
this.type = type;
}
public final Class<E> getType() {
return type;
}
@Override
public List<E> find(SearchParameters<E> sp) {
checkNotNull(sp, "The searchParameters cannot be null");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<E> criteriaQuery = builder.createQuery(type);
if (sp.isUseDistinct()) {
criteriaQuery.distinct(true);
}
Root<E> root = criteriaQuery.from(type);
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// fetches
jpaUtil.fetches(sp, root);
// order by
criteriaQuery.orderBy(orderByUtil.buildJpaOrders(sp.getOrders(), root, builder));
TypedQuery<E> typedQuery = entityManager.createQuery(criteriaQuery);
jpaUtil.applyPagination(typedQuery, sp);
List<E> entities = typedQuery.getResultList();
logger.trace("Returned {} elements for {}.", entities.size(), sp);
return entities;
}
@Override
public long findCount(SearchParameters<E> sp) {
checkNotNull(sp, "The searchParameters cannot be null");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
Root<E> root = criteriaQuery.from(type);
if (sp.isUseDistinct()) {
criteriaQuery = criteriaQuery.select(builder.countDistinct(root));
} else {
criteriaQuery = criteriaQuery.select(builder.count(root));
}
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// construct order by to fetch or joins if needed
orderByUtil.buildJpaOrders(sp.getOrders(), root, builder);
TypedQuery<Long> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getSingleResult().longValue();
}
@Override
public <T> List<T> findProperty(SearchParameters<E> sp, ResultParameters<E, T> resultParameters) {
checkNotNull(sp, "The searchParameters cannot be null");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = builder.createQuery(resultParameters.getTo());
if (sp.isUseDistinct()) {
criteriaQuery.distinct(true);
}
Root<E> root = criteriaQuery.from(type);
criteriaQuery.select(jpaUtil.<E, T> getPath(root, resultParameters.getPath()));
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// fetches
jpaUtil.fetches(sp, root);
// order by
criteriaQuery.orderBy(orderByUtil.buildJpaOrders(sp.getOrders(), root, builder));
TypedQuery<T> typedQuery = entityManager.createQuery(criteriaQuery);
jpaUtil.applyPagination(typedQuery, sp);
List<T> entities = typedQuery.getResultList();
logger.debug("Returned {} elements for {}.", entities.size(), sp);
return entities;
}
@Override
public long findPropertyCount(SearchParameters<E> sp, ResultParameters<E, ?> resultParameters) {
checkNotNull(sp, "The searchParameters cannot be null");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Long> criteriaQuery = builder.createQuery(Long.class);
Root<E> root = criteriaQuery.from(type);
Path<?> pathToCount = jpaUtil.getPath(root, resultParameters.getPath());
if (sp.isUseDistinct()) {
criteriaQuery = criteriaQuery.select(builder.countDistinct(pathToCount));
} else {
criteriaQuery = criteriaQuery.select(builder.count(pathToCount));
}
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// construct order by to fetch or joins if needed
orderByUtil.buildJpaOrders(sp.getOrders(), root, builder);
TypedQuery<Long> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getSingleResult().longValue();
}
@Override
public E findUnique(SearchParameters<E> sp) {
E result = findUniqueOrNone(sp);
if (result == null) {
throw new NoResultException("Developper: You expected 1 result but we found none !");
}
return result;
}
@Override
public E findUniqueOrNone(SearchParameters<E> sp) {
// this code is an optimization to prevent using a count
List<E> results = find(new SearchBuilder<E>(sp).paginate(0, 2).build());
if ((results == null) || results.isEmpty()) {
return null;
}
if (results.size() > 1) {
throw new NonUniqueResultException("Developper: You expected 1 result but we found more !");
}
return results.iterator().next();
}
protected <R> Predicate getPredicate(CriteriaQuery<?> criteriaQuery, Root<E> root, CriteriaBuilder builder, SearchParameters<E> sp) {
return jpaUtil.andPredicate(builder, //
bySearchPredicate(root, builder, sp), //
byMandatoryPredicate(criteriaQuery, root, builder, sp));
}
protected <R> Predicate bySearchPredicate(Root<E> root, CriteriaBuilder builder, SearchParameters<E> sp) {
return bySelectors(root, builder, sp);
}
protected Predicate bySelectors(Root<E> root, CriteriaBuilder builder, SearchParameters<E> sp) {
return bySelectorUtil.bySelectors(root, builder, sp);
}
/**
* You may override this method to add a Predicate to the default find
* method.
*/
protected <R> Predicate byMandatoryPredicate(CriteriaQuery<?> criteriaQuery, Root<E> root, CriteriaBuilder builder, SearchParameters<E> sp) {
return null;
}
// BEANS METHODS
protected final BySelectorUtil getBySelectorUtil() {
return bySelectorUtil;
}
@Inject
public final void setBySelectorUtil(BySelectorUtil bySelectorUtil) {
this.bySelectorUtil = bySelectorUtil;
}
protected final EntityManager getEntityManager() {
return entityManager;
}
@PersistenceContext
public final void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
protected final OrderByUtil getOrderByUtil() {
return orderByUtil;
}
@Inject
public final void setOrderByUtil(OrderByUtil orderByUtil) {
this.orderByUtil = orderByUtil;
}
protected final JpaUtil getJpaUtil() {
return jpaUtil;
}
@Inject
public final void setJpaUtil(JpaUtil jpaUtil) {
this.jpaUtil = jpaUtil;
}
protected final MetamodelUtil getMetamodelUtil() {
return metamodelUtil;
}
@Inject
public final void setMetamodelUtil(MetamodelUtil metamodelUtil) {
this.metamodelUtil = metamodelUtil;
}
}