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.mapping;
17
18 import lombok.EqualsAndHashCode;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Constructor;
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ConcurrentHashMap;
26
27 import org.springframework.beans.factory.annotation.Value;
28 import org.springframework.data.annotation.PersistenceConstructor;
29 import org.springframework.data.util.Lazy;
30 import org.springframework.data.util.TypeInformation;
31 import org.springframework.lang.Nullable;
32 import org.springframework.util.Assert;
33 import org.springframework.util.ReflectionUtils;
34 import org.springframework.util.StringUtils;
35
36 /**
37 * Value object to encapsulate the constructor to be used when mapping persistent data to objects.
38 *
39 * @author Oliver Gierke
40 * @author Jon Brisbin
41 * @author Thomas Darimont
42 * @author Christoph Strobl
43 * @author Mark Paluch
44 * @author Myeonghyeon Lee
45 */
46 public class PreferredConstructor<T, P extends PersistentProperty<P>> {
47
48 private final Constructor<T> constructor;
49 private final List<Parameter<Object, P>> parameters;
50 private final Map<PersistentProperty<?>, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>();
51
52 /**
53 * Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s.
54 *
55 * @param constructor must not be {@literal null}.
56 * @param parameters must not be {@literal null}.
57 */
58 @SafeVarargs
59 public PreferredConstructor(Constructor<T> constructor, Parameter<Object, P>... parameters) {
60
61 Assert.notNull(constructor, "Constructor must not be null!");
62 Assert.notNull(parameters, "Parameters must not be null!");
63
64 ReflectionUtils.makeAccessible(constructor);
65 this.constructor = constructor;
66 this.parameters = Arrays.asList(parameters);
67 }
68
69 /**
70 * Returns the underlying {@link Constructor}.
71 *
72 * @return
73 */
74 public Constructor<T> getConstructor() {
75 return constructor;
76 }
77
78 /**
79 * Returns the {@link Parameter}s of the constructor.
80 *
81 * @return
82 */
83 public List<Parameter<Object, P>> getParameters() {
84 return parameters;
85 }
86
87 /**
88 * Returns whether the constructor has {@link Parameter}s.
89 *
90 * @see #isNoArgConstructor()
91 * @return
92 */
93 public boolean hasParameters() {
94 return !parameters.isEmpty();
95 }
96
97 /**
98 * Returns whether the constructor does not have any arguments.
99 *
100 * @see #hasParameters()
101 * @return
102 */
103 public boolean isNoArgConstructor() {
104 return parameters.isEmpty();
105 }
106
107 /**
108 * Returns whether the constructor was explicitly selected (by {@link PersistenceConstructor}).
109 *
110 * @return
111 */
112 public boolean isExplicitlyAnnotated() {
113 return constructor.isAnnotationPresent(PersistenceConstructor.class);
114 }
115
116 /**
117 * Returns whether the given {@link PersistentProperty} is referenced in a constructor argument of the
118 * {@link PersistentEntity} backing this {@link PreferredConstructor}.
119 *
120 * @param property must not be {@literal null}.
121 * @return {@literal true} if the {@link PersistentProperty} is used in the constructor.
122 */
123 public boolean isConstructorParameter(PersistentProperty<?> property) {
124
125 Assert.notNull(property, "Property must not be null!");
126
127 Boolean cached = isPropertyParameterCache.get(property);
128
129 if (cached != null) {
130 return cached;
131 }
132
133 boolean result = false;
134 for (Parameter<?, P> parameter : parameters) {
135 if (parameter.maps(property)) {
136 isPropertyParameterCache.put(property, true);
137 result = true;
138 break;
139 }
140 }
141
142 return result;
143 }
144
145 /**
146 * Returns whether the given {@link Parameter} is one referring to an enclosing class. That is in case the class this
147 * {@link PreferredConstructor} belongs to is a member class actually. If that's the case the compiler creates a first
148 * constructor argument of the enclosing class type.
149 *
150 * @param parameter must not be {@literal null}.
151 * @return {@literal true} if the {@link PersistentProperty} maps to the enclosing class.
152 */
153 public boolean isEnclosingClassParameter(Parameter<?, P> parameter) {
154
155 Assert.notNull(parameter, "Parameter must not be null!");
156
157 if (parameters.isEmpty() || !parameter.isEnclosingClassParameter()) {
158 return false;
159 }
160
161 return parameters.get(0).equals(parameter);
162 }
163
164 /**
165 * Value object to represent constructor parameters.
166 *
167 * @param <T> the type of the parameter
168 * @author Oliver Gierke
169 */
170 @EqualsAndHashCode(exclude = { "enclosingClassCache", "hasSpelExpression" })
171 public static class Parameter<T, P extends PersistentProperty<P>> {
172
173 private final @Nullable String name;
174 private final TypeInformation<T> type;
175 private final String key;
176 private final @Nullable PersistentEntity<T, P> entity;
177
178 private final Lazy<Boolean> enclosingClassCache;
179 private final Lazy<Boolean> hasSpelExpression;
180
181 /**
182 * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of
183 * {@link Annotation}s. Will inspect the annotations for an {@link Value} annotation to lookup a key or an SpEL
184 * expression to be evaluated.
185 *
186 * @param name the name of the parameter, can be {@literal null}
187 * @param type must not be {@literal null}
188 * @param annotations must not be {@literal null} but can be empty
189 * @param entity must not be {@literal null}.
190 */
191 public Parameter(@Nullable String name, TypeInformation<T> type, Annotation[] annotations,
192 @Nullable PersistentEntity<T, P> entity) {
193
194 Assert.notNull(type, "Type must not be null!");
195 Assert.notNull(annotations, "Annotations must not be null!");
196
197 this.name = name;
198 this.type = type;
199 this.key = getValue(annotations);
200 this.entity = entity;
201
202 this.enclosingClassCache = Lazy.of(() -> {
203
204 if (entity == null) {
205 throw new IllegalStateException();
206 }
207
208 Class<T> owningType = entity.getType();
209 return owningType.isMemberClass() && type.getType().equals(owningType.getEnclosingClass());
210 });
211
212 this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression()));
213 }
214
215 @Nullable
216 private static String getValue(Annotation[] annotations) {
217
218 return Arrays.stream(annotations)//
219 .filter(it -> it.annotationType() == Value.class)//
220 .findFirst().map(it -> ((Value) it).value())//
221 .filter(StringUtils::hasText).orElse(null);
222 }
223
224 /**
225 * Returns the name of the parameter.
226 *
227 * @return
228 */
229 @Nullable
230 public String getName() {
231 return name;
232 }
233
234 /**
235 * Returns the {@link TypeInformation} of the parameter.
236 *
237 * @return
238 */
239 public TypeInformation<T> getType() {
240 return type;
241 }
242
243 /**
244 * Returns the raw resolved type of the parameter.
245 *
246 * @return
247 */
248 public Class<T> getRawType() {
249 return type.getType();
250 }
251
252 /**
253 * Returns the key to be used when looking up a source data structure to populate the actual parameter value.
254 *
255 * @return
256 */
257 public String getSpelExpression() {
258 return key;
259 }
260
261 /**
262 * Returns whether the constructor parameter is equipped with a SpEL expression.
263 *
264 * @return
265 */
266 public boolean hasSpelExpression() {
267 return this.hasSpelExpression.get();
268 }
269
270 /**
271 * Returns whether the {@link Parameter} maps the given {@link PersistentProperty}.
272 *
273 * @param property
274 * @return
275 */
276 boolean maps(PersistentProperty<?> property) {
277
278 PersistentEntity<T, P> entity = this.entity;
279 String name = this.name;
280
281 P referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name);
282
283 return property.equals(referencedProperty);
284 }
285
286 private boolean isEnclosingClassParameter() {
287 return enclosingClassCache.get();
288 }
289 }
290 }
291