package com.ctp.cdi.query.handler; import static com.ctp.cdi.query.util.EntityUtils.entityName; import static com.ctp.cdi.query.util.QueryUtils.isEmpty; import static com.ctp.cdi.query.util.QueryUtils.isString; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.metamodel.SingularAttribute; import org.jboss.solder.logging.Logger; import org.jboss.solder.properties.Property; import org.jboss.solder.properties.query.NamedPropertyCriteria; import org.jboss.solder.properties.query.PropertyQueries; import com.ctp.cdi.query.AbstractEntityDao; import com.ctp.cdi.query.EntityDao; import com.ctp.cdi.query.builder.QueryBuilder; import com.ctp.cdi.query.spi.DelegateQueryHandler; import com.ctp.cdi.query.spi.QueryInvocationContext; /** * Implement basic functionality from the {@link EntityDao}. * * @author thomashug * * @param <E> Entity type. * @param <PK> Primary key type, must be a serializable. */ public class EntityDaoHandler<E, PK extends Serializable> extends AbstractEntityDao<E, PK> implements DelegateQueryHandler { private final Logger log = Logger.getLogger(EntityDaoHandler.class); @Inject private QueryInvocationContext context; @Override public E save(E entity) { if (context.isNew(entity)) { entityManager().persist(entity); return entity; } return entityManager().merge(entity); } @Override public E saveAndFlush(E entity) { E result = save(entity); entityManager().flush(); return result; } @Override public void refresh(E entity) { entityManager().refresh(entity); } @Override public E findBy(PK primaryKey) { return entityManager().find(entityClass(), primaryKey); } @Override public List<E> findBy(E example, SingularAttribute<E, ?>... attributes) { return findBy(example, -1, -1, attributes); } @Override public List<E> findBy(E example, int start, int max, SingularAttribute<E, ?>... attributes) { return executeExampleQuery(example,start,max,false,attributes); } @Override public List<E> findByLike(E example, SingularAttribute<E, ?>... attributes) { return findByLike(example, -1, -1, attributes); } @Override public List<E> findByLike(E example, int start, int max, SingularAttribute<E, ?>... attributes) { return executeExampleQuery(example,start,max,true,attributes); } @Override public List<E> findAll() { return entityManager().createQuery(allQuery(), entityClass()).getResultList(); } @Override public List<E> findAll(int start, int max) { TypedQuery<E> query = entityManager().createQuery(allQuery(), entityClass()); if (start > 0) { query.setFirstResult(start); } if (max > 0) { query.setMaxResults(max); } return query.getResultList(); } @Override public Long count() { return entityManager().createQuery(countQuery(), Long.class).getSingleResult(); } @Override public Long count(E example, SingularAttribute<E, ?>... attributes) { return executeCountQuery(example,false,attributes); } @Override public Long countLike(E example, SingularAttribute<E, ?>... attributes) { return executeCountQuery(example,true,attributes); } @Override public void remove(E entity) { entityManager().remove(entity); } @Override public void flush() { entityManager().flush(); } @Override public EntityManager entityManager() { return context.getEntityManager(); } @Override public CriteriaQuery<E> criteriaQuery() { return entityManager().getCriteriaBuilder().createQuery(entityClass()); } @Override @SuppressWarnings("unchecked") public Class<E> entityClass() { return (Class<E>) context.getEntityClass(); } // ---------------------------------------------------------------------------- // PRIVATE // ---------------------------------------------------------------------------- private String allQuery() { return QueryBuilder.selectQuery(entityName(entityClass())); } private String countQuery() { return QueryBuilder.countQuery(entityName(entityClass())); } private String exampleQuery(String queryBase, List<Property<Object>> properties, boolean useLikeOperator) { StringBuilder jpqlQuery = new StringBuilder(queryBase).append(" where "); jpqlQuery.append(prepareWhere(properties, useLikeOperator)); return jpqlQuery.toString(); } private void addParameters(TypedQuery<?> query, E example, List<Property<Object>> properties, boolean useLikeOperator) { for (Property<Object> property : properties) { property.setAccessible(); query.setParameter(property.getName(), transform(property.getValue(example), useLikeOperator)); } } private Object transform(Object value, final boolean useLikeOperator) { if (value != null && useLikeOperator && isString(value)) { // seems to be an OpenJPA bug: // parameters in querys fail validation, e.g. UPPER(e.name) like UPPER(:name) String result = ((String) value).toUpperCase(); return "%" + result + "%"; } return value; } private String prepareWhere(List<Property<Object>> properties, boolean useLikeOperator) { Iterator<Property<Object>> iterator = properties.iterator(); StringBuilder result = new StringBuilder(); while (iterator.hasNext()) { Property<Object> property = iterator.next(); String name = property.getName(); if (useLikeOperator && property.getJavaClass().getName().equals(String.class.getName())) { result.append("UPPER(e.").append(name).append(") like :").append(name) .append(iterator.hasNext() ? " and " : ""); } else { result.append("e.").append(name).append(" = :").append(name).append(iterator.hasNext() ? " and " : ""); } } return result.toString(); } private List<String> extractPropertyNames(SingularAttribute<E, ?>... attributes) { List<String> result = new ArrayList<String>(attributes.length); for (SingularAttribute<E, ?> attribute : attributes) { result.add(attribute.getName()); } return result; } private List<Property<Object>> extractProperties(SingularAttribute<E, ?>... attributes) { List<String> names = extractPropertyNames(attributes); List<Property<Object>> properties = PropertyQueries.createQuery(entityClass()) .addCriteria(new NamedPropertyCriteria(names.toArray(new String[] {}))).getResultList(); return properties; } private List<E> executeExampleQuery(E example, int start, int max, boolean useLikeOperator, SingularAttribute<E, ?>... attributes) { //Not sure if this should be the intended behaviour //when we don't get any attributes maybe we should //return a empty list instead of all results if (isEmpty(attributes)) { return findAll(start, max); } List<Property<Object>> properties = extractProperties(attributes); String jpqlQuery = exampleQuery(allQuery(), properties, useLikeOperator); log.debugv("findBy|findByLike: Created query {0}", jpqlQuery); TypedQuery<E> query = entityManager().createQuery(jpqlQuery, entityClass()); // set starting position if (start > 0) { query.setFirstResult(start); } // set maximum results if (max > 0) { query.setMaxResults(max); } addParameters(query, example, properties, useLikeOperator); return query.getResultList(); } private Long executeCountQuery(E example, boolean useLikeOperator, SingularAttribute<E, ?>... attributes) { if (isEmpty(attributes)) { return count(); } List<Property<Object>> properties = extractProperties(attributes); String jpqlQuery = exampleQuery(countQuery(), properties, useLikeOperator); log.debugv("count: Created query {0}", jpqlQuery); TypedQuery<Long> query = entityManager().createQuery(jpqlQuery, Long.class); addParameters(query, example, properties, useLikeOperator); return query.getSingleResult(); } }