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