/* * Copyright 2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.geequery.springdata.repository.support; import java.util.ArrayList; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.Predicate; import javax.persistence.metamodel.SingularAttribute; import jef.database.Field; import jef.database.QB; import jef.database.dialect.type.ColumnMapping; import jef.database.meta.FBIField; import jef.database.meta.ITableMetadata; import jef.database.query.Query; import org.springframework.data.domain.Example; import org.springframework.data.domain.ExampleMatcher; import org.springframework.data.repository.core.support.ExampleMatcherAccessor; import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * {@link QueryByExamplePredicateBuilder} creates a single * {@link CriteriaBuilder#and(Predicate...)} combined {@link Predicate} for a * given {@link Example}. <br /> * The builder includes any {@link SingularAttribute} of the * {@link Example#getProbe()} applying {@link String} and {@literal null} * matching strategies configured on the {@link Example}. Ignored paths are no * matter of their actual value not considered. <br /> * */ public class QueryByExamplePredicateBuilder { /** * Extract the {@link Predicate} representing the {@link Example}. * * @param root * must not be {@literal null}. * @param cb * must not be {@literal null}. * @param example * must not be {@literal null}. * @return never {@literal null}. */ public static Query<?> getPredicate(ITableMetadata meta, Example<?> example) { Assert.notNull(meta, "meta must not be null!"); Assert.notNull(example, "Example must not be null!"); Query<?> q = QB.create(meta); getPredicates(q, "", meta, example.getProbe(), example.getProbeType(), new ExampleMatcherAccessor(example.getMatcher()), new PathNode("root", null, example.getProbe())); return q; } static void getPredicates(Query<?> q, String path, ITableMetadata type, Object value, Class<?> probeType, ExampleMatcherAccessor exampleAccessor, PathNode currentNode) { // List<Predicate> predicates = new ArrayList<Predicate>(); DirectFieldAccessFallbackBeanWrapper beanWrapper = new DirectFieldAccessFallbackBeanWrapper(value); for (ColumnMapping attribute : type.getColumns()) { String fieldName = attribute.fieldName(); String currentPath = !StringUtils.hasText(path) ? fieldName : path + "." + fieldName; if (exampleAccessor.isIgnoredPath(currentPath)) { continue; } Object attributeValue = exampleAccessor.getValueTransformerForPath(currentPath).convert(beanWrapper.getPropertyValue(fieldName)); if (attributeValue == null) { if (exampleAccessor.getNullHandler().equals(ExampleMatcher.NullHandler.INCLUDE)) { q.addCondition(QB.isNull(type.getField(fieldName))); } continue; } // 这种情况不存在 // if // (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED)) // { // // predicates.addAll(getPredicates(currentPath, cb, // from.get(attribute.getName()), // (ManagedType<?>) attribute.getType(), attributeValue, probeType, // exampleAccessor, currentNode)); // continue; // } Field expression = attribute.field(); if (attribute.getFieldType().equals(String.class)) { // 全转小写处理 if (exampleAccessor.isIgnoreCaseForPath(currentPath)) { expression = new FBIField("lower(" + expression.name() + ")", q); attributeValue = attributeValue.toString().toLowerCase(); } // 根据运算符计算String的运算符 switch (exampleAccessor.getStringMatcherForPath(currentPath)) { case DEFAULT: case EXACT: q.addCondition(QB.eq(expression, attributeValue)); break; case CONTAINING: q.addCondition(QB.matchAny(expression, String.valueOf(attributeValue))); break; case STARTING: q.addCondition(QB.matchStart(expression, String.valueOf(attributeValue))); break; case ENDING: q.addCondition(QB.matchEnd(expression, String.valueOf(attributeValue))); break; default: throw new IllegalArgumentException("Unsupported StringMatcher " + exampleAccessor.getStringMatcherForPath(currentPath)); } } else { q.addCondition(QB.eq(expression, attributeValue)); } } // 处理位于引用字段(关系对象)上的条件,先不支持,后续再加 // if (isAssociation(attribute)) { // // if (!(from instanceof From)) { // throw new JpaSystemException(new IllegalArgumentException( // String.format("Unexpected path type for %s. Found % where From.class was expected.", // currentPath, from))); // } // // PathNode node = currentNode.add(attribute.getName(), attributeValue); // if (node.spansCycle()) { // throw new InvalidDataAccessApiUsageException( // String.format("Path '%s' from root %s must not span a cyclic property reference!\r\n%s", // currentPath, // ClassUtils.getShortName(probeType), node)); // } // // predicates.addAll(getPredicates(currentPath, cb, ((From<?, ?>) // from).join(attribute.getName()), // (ManagedType<?>) attribute.getType(), attributeValue, probeType, // exampleAccessor, node)); } /** * {@link PathNode} is used to dynamically grow a directed graph structure * that allows to detect cycles within its direct predecessor nodes by * comparing parent node values using * {@link System#identityHashCode(Object)}. * * @author Christoph Strobl */ private static class PathNode { String name; PathNode parent; List<PathNode> siblings = new ArrayList<PathNode>();; Object value; public PathNode(String edge, PathNode parent, Object value) { this.name = edge; this.parent = parent; this.value = value; } PathNode add(String attribute, Object value) { PathNode node = new PathNode(attribute, this, value); siblings.add(node); return node; } boolean spansCycle() { if (value == null) { return false; } String identityHex = ObjectUtils.getIdentityHexString(value); PathNode tmp = parent; while (tmp != null) { if (ObjectUtils.getIdentityHexString(tmp.value).equals(identityHex)) { return true; } tmp = tmp.parent; } return false; } @Override public String toString() { StringBuilder sb = new StringBuilder(); if (parent != null) { sb.append(parent.toString()); sb.append(" -"); sb.append(name); sb.append("-> "); } sb.append("[{ "); sb.append(ObjectUtils.nullSafeToString(value)); sb.append(" }]"); return sb.toString(); } } }