1 /*
2  * Copyright 2011-2020 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.data.querydsl;
17
18 import java.lang.reflect.Field;
19 import java.lang.reflect.Modifier;
20 import java.util.Optional;
21
22 import org.springframework.util.Assert;
23 import org.springframework.util.ClassUtils;
24 import org.springframework.util.ReflectionUtils;
25
26 import com.querydsl.core.types.EntityPath;
27
28 /**
29  * Simple implementation of {@link EntityPathResolver} to lookup a query class by reflection and using the static field
30  * of the same type.
31  *
32  * @author Oliver Gierke
33  * @author Jens Schauder
34  */

35 public class SimpleEntityPathResolver implements EntityPathResolver {
36
37     private static final String NO_CLASS_FOUND_TEMPLATE = "Did not find a query class %s for domain class %s!";
38     private static final String NO_FIELD_FOUND_TEMPLATE = "Did not find a static field of the same type in %s!";
39
40     public static final SimpleEntityPathResolver INSTANCE = new SimpleEntityPathResolver("");
41
42     private final String querySuffix;
43
44     /**
45      * Creates a new {@link SimpleEntityPathResolver} with the given query package suffix.
46      * 
47      * @param querySuffix must not be {@literal null}.
48      */

49     public SimpleEntityPathResolver(String querySuffix) {
50
51         Assert.notNull(querySuffix, "Query suffix must not be null!");
52
53         this.querySuffix = querySuffix;
54     }
55
56     /**
57      * Creates an {@link EntityPath} instance for the given domain class. Tries to lookup a class matching the naming
58      * convention (prepend Q to the simple name of the class, same package) and find a static field of the same type in
59      * it.
60      *
61      * @param domainClass
62      * @return
63      */

64     @SuppressWarnings("unchecked")
65     public <T> EntityPath<T> createPath(Class<T> domainClass) {
66
67         String pathClassName = getQueryClassName(domainClass);
68
69         try {
70
71             Class<?> pathClass = ClassUtils.forName(pathClassName, domainClass.getClassLoader());
72
73             return getStaticFieldOfType(pathClass)//
74                     .map(it -> (EntityPath<T>) ReflectionUtils.getField(it, null))//
75                     .orElseThrow(() -> new IllegalStateException(String.format(NO_FIELD_FOUND_TEMPLATE, pathClass)));
76
77         } catch (ClassNotFoundException e) {
78             throw new IllegalArgumentException(String.format(NO_CLASS_FOUND_TEMPLATE, pathClassName, domainClass.getName()),
79                     e);
80         }
81     }
82
83     /**
84      * Returns the first static field of the given type inside the given type.
85      *
86      * @param type
87      * @return
88      */

89     private Optional<Field> getStaticFieldOfType(Class<?> type) {
90
91         for (Field field : type.getDeclaredFields()) {
92
93             boolean isStatic = Modifier.isStatic(field.getModifiers());
94             boolean hasSameType = type.equals(field.getType());
95
96             if (isStatic && hasSameType) {
97                 return Optional.of(field);
98             }
99         }
100
101         Class<?> superclass = type.getSuperclass();
102         return Object.class.equals(superclass) ? Optional.empty() : getStaticFieldOfType(superclass);
103     }
104
105     /**
106      * Returns the name of the query class for the given domain class.
107      *
108      * @param domainClass
109      * @return
110      */

111     private String getQueryClassName(Class<?> domainClass) {
112
113         String simpleClassName = ClassUtils.getShortName(domainClass);
114         String packageName = domainClass.getPackage().getName();
115
116         return String.format("%s%s.Q%s%s", packageName, querySuffix, getClassBase(simpleClassName),
117                 domainClass.getSimpleName());
118     }
119
120     /**
121      * Analyzes the short class name and potentially returns the outer class.
122      *
123      * @param shortName
124      * @return
125      */

126     private String getClassBase(String shortName) {
127
128         String[] parts = shortName.split("\\.");
129
130         return parts.length < 2 ? "" : parts[0] + "_";
131     }
132 }
133