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    
021    package org.granite.tide.spring.data;
022    
023    import java.beans.BeanInfo;
024    import java.beans.Introspector;
025    import java.beans.PropertyDescriptor;
026    import java.util.ArrayList;
027    import java.util.List;
028    
029    import javax.persistence.criteria.CriteriaBuilder;
030    import javax.persistence.criteria.CriteriaQuery;
031    import javax.persistence.criteria.Path;
032    import javax.persistence.criteria.Predicate;
033    import javax.persistence.criteria.Root;
034    import javax.persistence.metamodel.Attribute;
035    import javax.persistence.metamodel.ManagedType;
036    import javax.persistence.metamodel.Metamodel;
037    
038    import org.springframework.data.jpa.domain.Specification;
039    
040    public 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    }