package org.tynamo.model.jpa; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.persistence.EntityManager; 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 javax.persistence.metamodel.EntityType; import javax.persistence.metamodel.SingularAttribute; import org.apache.tapestry5.grid.GridDataSource; import org.apache.tapestry5.grid.SortConstraint; import org.tynamo.descriptor.TynamoPropertyDescriptor; import org.tynamo.search.SearchFilterPredicate; public class SearchableJpaGridDataSource implements GridDataSource { private Map<TynamoPropertyDescriptor, SearchFilterPredicate> propertySearchFilterMap; private EntityManager entityManager; private Class entityType; private int startIndex; private List preparedResults; private String[] searchTerms; private List<TynamoPropertyDescriptor> searchablePropertyDescriptors; private Set includedIds; public SearchableJpaGridDataSource(EntityManager entityManager, Class entityType, Map<TynamoPropertyDescriptor, SearchFilterPredicate> propertySearchFilterMap, List<TynamoPropertyDescriptor> searchablePropertyDescriptors, String... searchTerms) { this(entityManager, entityType, propertySearchFilterMap); this.searchablePropertyDescriptors = searchablePropertyDescriptors; this.searchTerms = searchTerms == null ? new String[0] : searchTerms; } public SearchableJpaGridDataSource(EntityManager entityManager, Class entityType, Set includedIds, Map<TynamoPropertyDescriptor, SearchFilterPredicate> propertySearchFilterMap) { this(entityManager, entityType, propertySearchFilterMap); this.includedIds = includedIds; } private SearchableJpaGridDataSource(EntityManager entityManager, Class entityType, Map<TynamoPropertyDescriptor, SearchFilterPredicate> propertySearchFilterMap) { this.entityManager = entityManager; this.entityType = entityType; this.propertySearchFilterMap = propertySearchFilterMap; } /** * {@inheritDoc} */ public int getAvailableRows() { final CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> criteria = builder.createQuery(Long.class); final Root<?> root = criteria.from(entityType); criteria = criteria.select(builder.count(root)); applyAdditionalConstraints(criteria, root, builder); return entityManager.createQuery(criteria).getSingleResult().intValue(); } /** * {@inheritDoc} */ public void prepare(final int startIndex, final int endIndex, final List<SortConstraint> sortConstraints) { final CriteriaBuilder builder = entityManager.getCriteriaBuilder(); final CriteriaQuery criteria = builder.createQuery(entityType); final Root root = criteria.from(entityType); applyAdditionalConstraints(criteria.select(root), root, builder); for (final SortConstraint constraint : sortConstraints) { final String propertyName = constraint.getPropertyModel().getPropertyName(); final Path<Object> propertyPath = root.get(propertyName); switch (constraint.getColumnSort()) { case ASCENDING: criteria.orderBy(builder.asc(propertyPath)); break; case DESCENDING: criteria.orderBy(builder.desc(propertyPath)); break; default: } } final TypedQuery<?> query = entityManager.createQuery(criteria); query.setFirstResult(startIndex); query.setMaxResults(endIndex - startIndex + 1); this.startIndex = startIndex; preparedResults = query.getResultList(); } protected void applyAdditionalConstraints(final CriteriaQuery<?> criteria, final Root<?> root, final CriteriaBuilder builder) { List<Predicate> predicates = new ArrayList<Predicate>(); if (searchTerms != null && searchTerms.length > 0) { for (TynamoPropertyDescriptor searchableProperty : searchablePropertyDescriptors) { for (String searchTerm : searchTerms) predicates.add(builder.like(root.<String>get(searchableProperty.getName()), searchTerm)); } Predicate predicate = builder.or(predicates.toArray(new Predicate[predicates.size()])); criteria.where(applyAdditionalConstraints(builder, root, predicate)); } else { Predicate predicate = applyAdditionalConstraints(builder, root, null); if (predicate != null) criteria.where(predicate); } } /** * {@inheritDoc} */ public Object getRowValue(final int index) { return preparedResults.get(index - startIndex); } /** * {@inheritDoc} */ public Class<?> getRowType() { return entityType; } /** * Invoked after the main criteria has been set up (firstResult, maxResults and any sort contraints). This gives subclasses a chance to * apply additional constraints before the list of results is obtained from the criteria. This implementation does nothing and may be * overridden. */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected Predicate applyAdditionalConstraints(CriteriaBuilder builder, Root<?> root, Predicate existingPredicate) { if ((includedIds == null || includedIds.size() <= 0) && (propertySearchFilterMap == null || propertySearchFilterMap.size() <= 0)) return existingPredicate; List<Predicate> predicates = new ArrayList<Predicate>(propertySearchFilterMap.entrySet().size()); if (existingPredicate != null) predicates.add(existingPredicate); if (includedIds != null) { EntityType entityType = root.getModel(); SingularAttribute idAttr = entityType.getId(entityType.getIdType().getJavaType()); predicates.add(root.get(idAttr).in(includedIds)); } for (Entry<TynamoPropertyDescriptor, SearchFilterPredicate> entry : propertySearchFilterMap.entrySet()) predicates.add(createPredicate(builder, root, entry.getKey().getName(), entry.getValue())); return builder.and(predicates.toArray(new Predicate[predicates.size()])); } @SuppressWarnings({ "unchecked", "rawtypes" }) private Predicate createPredicate(CriteriaBuilder builder, Root<?> root, String propertyName, SearchFilterPredicate predicate) { switch (predicate.getOperator()) { case eq: return builder.equal(root.get(propertyName), predicate.getLowValue()); case ne: return builder.notEqual(root.get(propertyName), predicate.getLowValue()); case gt: return builder.greaterThan((Path<Comparable>) root.<Comparable> get(propertyName), (Comparable) predicate.getLowValue()); case ge: return builder.greaterThanOrEqualTo((Path<Comparable>) root.<Comparable> get(propertyName), (Comparable) predicate.getLowValue()); case lt: return builder.lessThan((Path<Comparable>) root.<Comparable> get(propertyName), (Comparable) predicate.getLowValue()); case le: return builder.lessThanOrEqualTo((Path<Comparable>) root.<Comparable> get(propertyName), (Comparable) predicate.getLowValue()); default: throw new IllegalArgumentException("Search filtering for operator '" + predicate.getOperator() + "' is not yet supported"); } } }