001/*
002  GRANITE DATA SERVICES
003  Copyright (C) 2011 GRANITE DATA SERVICES S.A.S.
004
005  This file is part of Granite Data Services.
006
007  Granite Data Services is free software; you can redistribute it and/or modify
008  it under the terms of the GNU Library General Public License as published by
009  the Free Software Foundation; either version 2 of the License, or (at your
010  option) any later version.
011
012  Granite Data Services is distributed in the hope that it will be useful, but
013  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015  for more details.
016
017  You should have received a copy of the GNU Library General Public License
018  along with this library; if not, see <http://www.gnu.org/licenses/>.
019*/
020
021package org.granite.tide.spring.data;
022
023import java.beans.BeanInfo;
024import java.beans.Introspector;
025import java.beans.PropertyDescriptor;
026import java.util.ArrayList;
027import java.util.List;
028
029import javax.persistence.criteria.CriteriaBuilder;
030import javax.persistence.criteria.CriteriaQuery;
031import javax.persistence.criteria.Path;
032import javax.persistence.criteria.Predicate;
033import javax.persistence.criteria.Root;
034import javax.persistence.metamodel.Attribute;
035import javax.persistence.metamodel.ManagedType;
036import javax.persistence.metamodel.Metamodel;
037
038import org.springframework.data.jpa.domain.Specification;
039
040public class FilterExampleSpecification<T> implements Specification<T> {
041        
042        private Metamodel metamodel;
043        private Object filter;
044        
045        private FilterExampleSpecification(Metamodel metamodel, Object filter) {
046                this.metamodel = metamodel;
047                this.filter = filter;
048        }
049        
050        public static <T> FilterExampleSpecification<T> byExample(Metamodel metamodel, Object filter) {
051                return new FilterExampleSpecification<T>(metamodel, filter);
052        }
053        
054        public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
055                List<Predicate> predicates = new ArrayList<Predicate>();
056                
057                applyAttributes(predicates, root, builder, metamodel.entity(filter.getClass()), filter);
058                
059                if (predicates.size() > 0)
060                        return builder.and(predicates.toArray(new Predicate[predicates.size()]));
061                
062                return null;
063        }
064        
065        private void applyAttributes(List<Predicate> predicates, Path<?> root, CriteriaBuilder builder, ManagedType<?> filterType, Object filter) {
066                // Query by example : the filter is of the same type as the entity
067                PropertyDescriptor[] pds = null;
068                try {
069                        // Query by bean filter
070                        BeanInfo info = Introspector.getBeanInfo(filter.getClass());
071                        pds = info.getPropertyDescriptors();
072                }
073                catch (Exception e) {
074                        throw new RuntimeException("Could not introspect filter bean", e);
075                }
076                
077                for (PropertyDescriptor pd : pds) {
078                        Attribute<?, ?> attribute = filterType.getAttribute(pd.getName());
079                        if (attribute == null)
080                                continue;
081                        
082                        if (attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED) {
083                                // Visit embedded elements recursively
084                                try {
085                                        Object embedded = pd.getReadMethod().invoke(filter);
086                                        if (embedded != null) {
087                                                ManagedType<?> embeddedType = metamodel.embeddable(attribute.getJavaType());
088                                                applyAttributes(predicates, root.get(pd.getName()), builder, embeddedType, embedded);
089                                        }
090                                }
091                                catch (Exception e) {
092                                        throw new RuntimeException("Could not get filter property " + pd.getName(), e);
093                                }
094                                
095                                continue;
096                        }
097                        
098                        if (attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC)
099                                continue;
100                        
101                        Object value = null;
102                        try {
103                                value = pd.getReadMethod().invoke(filter);
104                        }
105                        catch (Exception e) {
106                                throw new RuntimeException("Could not get filter property " + pd.getName(), e);
107                        }
108                        
109                        Predicate predicate = FilterSpecUtil.buildPredicate(root, builder, pd.getReadMethod().getReturnType(), pd.getName(), value);
110                        if (predicate != null)
111                                predicates.add(predicate);
112                }
113        }
114}