package io.oasp.module.jpa.dataaccess.base; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityNotFoundException; import javax.persistence.LockModeType; 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.Root; import net.sf.mmm.util.entity.api.PersistenceEntity; import net.sf.mmm.util.exception.api.ObjectNotFoundUserException; import net.sf.mmm.util.search.base.AbstractSearchCriteria; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mysema.query.jpa.impl.JPAQuery; import com.mysema.query.types.Expression; import io.oasp.module.jpa.common.api.to.PaginatedListTo; import io.oasp.module.jpa.common.api.to.PaginationResultTo; import io.oasp.module.jpa.common.api.to.PaginationTo; import io.oasp.module.jpa.common.api.to.SearchCriteriaTo; import io.oasp.module.jpa.dataaccess.api.GenericDao; /** * This is the abstract base-implementation of the {@link GenericDao} interface. * * @param <ID> is the generic type if the {@link PersistenceEntity#getId() primary key}. * @param <E> is the generic type of the managed {@link PersistenceEntity}. * */ // @Repository public abstract class AbstractGenericDao<ID, E extends PersistenceEntity<ID>> implements GenericDao<ID, E> { /** Logger instance. */ private static final Logger LOG = LoggerFactory.getLogger(AbstractGenericDao.class); private EntityManager entityManager; /** * The constructor. */ public AbstractGenericDao() { super(); } /** * @return the {@link Class} reflecting the managed entity. */ protected abstract Class<E> getEntityClass(); /** * @return the {@link EntityManager} instance. */ protected EntityManager getEntityManager() { return this.entityManager; } /** * @param entityManager the {@link EntityManager} to inject. */ @PersistenceContext public void setEntityManager(EntityManager entityManager) { this.entityManager = entityManager; } /** * @return the name of the managed entity. */ protected String getEntityName() { return getEntityClass().getSimpleName(); } @Override public E save(E entity) { if (isNew(entity)) { getEntityManager().persist(entity); LOG.debug("Saved new {} with id {}.", getEntityName(), entity.getId()); return entity; } else { if (getEntityManager().find(entity.getClass(), entity.getId()) != null) { E update = getEntityManager().merge(entity); LOG.debug("Updated {} with id {}.", getEntityName(), entity.getId()); return update; } else { throw new EntityNotFoundException("Entity not found"); } } } /** * Determines if the given {@link PersistenceEntity} is {@link PersistenceEntity#STATE_NEW new}. * * @param entity is the {@link PersistenceEntity} to check. * @return {@code true} if {@link PersistenceEntity#STATE_NEW new}, {@code false} otherwise (e.g. * {@link PersistenceEntity#STATE_DETACHED detached} or {@link PersistenceEntity#STATE_MANAGED managed}. */ protected boolean isNew(E entity) { return entity.getId() == null; } @Override public void save(Iterable<? extends E> entities) { for (E entity : entities) { save(entity); } } @Override public void forceIncrementModificationCounter(E entity) { getEntityManager().lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT); } @Override public E findOne(ID id) { E entity = getEntityManager().find(getEntityClass(), id); return entity; } @Override public E find(ID id) throws ObjectNotFoundUserException { E entity = findOne(id); if (entity == null) { throw new ObjectNotFoundUserException(getEntityClass().getSimpleName(), id); } return entity; } @Override public boolean exists(ID id) { // pointless... return findOne(id) != null; } /** * @return an {@link Iterable} to find ALL {@link #getEntityClass() managed entities} from the persistent store. Not * exposed to API by default as this might not make sense for all kind of entities. */ public List<E> findAll() { CriteriaQuery<E> query = getEntityManager().getCriteriaBuilder().createQuery(getEntityClass()); Root<E> root = query.from(getEntityClass()); query.select(root); TypedQuery<E> typedQuery = getEntityManager().createQuery(query); List<E> resultList = typedQuery.getResultList(); LOG.debug("Query for all {} objects returned {} hit(s).", getEntityName(), resultList.size()); return resultList; } @Override public List<E> findAll(Iterable<ID> ids) { CriteriaBuilder builder = getEntityManager().getCriteriaBuilder(); CriteriaQuery<E> query = builder.createQuery(getEntityClass()); Root<E> root = query.from(getEntityClass()); query.select(root); query.where(root.get("id").in(ids)); TypedQuery<E> typedQuery = getEntityManager().createQuery(query); List<E> resultList = typedQuery.getResultList(); LOG.debug("Query for selection of {} objects returned {} hit(s).", getEntityName(), resultList.size()); return resultList; } @Override public void delete(ID id) { E entity = getEntityManager().getReference(getEntityClass(), id); getEntityManager().remove(entity); LOG.debug("Deleted {} with ID {}.", getEntityName(), id); } @Override public void delete(E entity) { // entity might be detached and could cause trouble in entityManager on remove if (getEntityManager().contains(entity)) { getEntityManager().remove(entity); LOG.debug("Deleted {} with ID {}.", getEntityName(), entity.getId()); } else { delete(entity.getId()); } } @Override public void delete(Iterable<? extends E> entities) { for (E entity : entities) { delete(entity); } } @SuppressWarnings("javadoc") protected PaginatedListTo<E> findPaginated(SearchCriteriaTo criteria, Query query, Expression<E> expr) { throw new UnsupportedOperationException("Pagination is not yet supported for generic JPA queries."); } /** * Returns a paginated list of entities according to the supplied {@link SearchCriteriaTo criteria}. * <p> * Applies {@code limit} and {@code offset} values to the supplied {@code query} according to the supplied * {@link PaginationTo pagination} information inside {@code criteria}. * <p> * If a {@link PaginationTo#isTotal() total count} of available entities is requested, will also execute a second * query, without pagination parameters applied, to obtain said count. * <p> * Will install a query timeout if {@link SearchCriteriaTo#getSearchTimeout()} is not null. * * @param criteria contains information about the requested page. * @param query is a query which is preconfigured with the desired conditions for the search. * @param expr is used for the final mapping from the SQL result to the entities. * @return a paginated list. */ protected PaginatedListTo<E> findPaginated(SearchCriteriaTo criteria, JPAQuery query, Expression<E> expr) { applyCriteria(criteria, query); PaginationTo pagination = criteria.getPagination(); PaginationResultTo paginationResult = createPaginationResult(pagination, query); applyPagination(pagination, query); List<E> paginatedList = query.list(expr); return new PaginatedListTo<>(paginatedList, paginationResult); } /** * Creates a {@link PaginationResultTo pagination result} for the given {@code pagination} and {@code query}. * <p> * Needs to be called before pagination is applied to the {@code query}. * * @param pagination contains information about the requested page. * @param query is a query preconfigured with the desired conditions for the search. * @return information about the applied pagination. */ protected PaginationResultTo createPaginationResult(PaginationTo pagination, JPAQuery query) { Long total = calculateTotalBeforePagination(pagination, query); return new PaginationResultTo(pagination, total); } /** * Calculates the total number of entities the given {@link JPAQuery query} would return without pagination applied. * <p> * Needs to be called before pagination is applied to the {@code query}. * * @param pagination is the pagination information as requested by the client. * @param query is the {@link JPAQuery query} for which to calculate the total. * @return the total count, or {@literal null} if {@link PaginationTo#isTotal()} is {@literal false}. */ protected Long calculateTotalBeforePagination(PaginationTo pagination, JPAQuery query) { Long total = null; if (pagination.isTotal()) { total = query.clone().count(); } return total; } /** * Applies the {@link PaginationTo pagination criteria} to the given {@link JPAQuery}. * * @param pagination is the {@link PaginationTo pagination criteria} to apply. * @param query is the {@link JPAQuery} to apply to. */ protected void applyPagination(PaginationTo pagination, JPAQuery query) { if (pagination == PaginationTo.NO_PAGINATION) { return; } Integer limit = pagination.getSize(); if (limit != null) { query.limit(limit); int page = pagination.getPage(); if (page > 0) { query.offset((page - 1) * limit); } } } /** * Applies the meta-data of the given {@link AbstractSearchCriteria search criteria} to the given {@link JPAQuery}. * * @param criteria is the {@link AbstractSearchCriteria search criteria} to apply. * @param query is the {@link JPAQuery} to apply to. */ protected void applyCriteria(AbstractSearchCriteria criteria, JPAQuery query) { Integer limit = criteria.getMaximumHitCount(); if (limit != null) { query.limit(limit); } int offset = criteria.getHitOffset(); if (offset > 0) { query.offset(offset); } Long timeout = criteria.getSearchTimeout(); if (timeout != null) { query.setHint("javax.persistence.query.timeout", timeout.intValue()); } } /** * Applies the meta-data of the given {@link AbstractSearchCriteria search criteria} to the given {@link Query}. * * @param criteria is the {@link AbstractSearchCriteria search criteria} to apply. * @param query is the {@link Query} to apply to. */ protected void applyCriteria(AbstractSearchCriteria criteria, Query query) { Integer limit = criteria.getMaximumHitCount(); if (limit != null) { query.setMaxResults(limit); } int offset = criteria.getHitOffset(); if (offset > 0) { query.setFirstResult(offset); } Long timeout = criteria.getSearchTimeout(); if (timeout != null) { query.setHint("javax.persistence.query.timeout", timeout.intValue()); } } /** * Applies the meta-data of the given {@link SearchCriteriaTo search criteria} to the given {@link Query}. * * @param criteria is the {@link AbstractSearchCriteria search criteria} to apply. * @param query is the {@link JPAQuery} to apply to. */ protected void applyCriteria(SearchCriteriaTo criteria, JPAQuery query) { Integer timeout = criteria.getSearchTimeout(); if (timeout != null) { query.setHint("javax.persistence.query.timeout", timeout.intValue()); } } }