diff --git a/src/main/asciidoc/object-mapping.adoc b/src/main/asciidoc/object-mapping.adoc index 2e01cdfd4..5f8592996 100644 --- a/src/main/asciidoc/object-mapping.adoc +++ b/src/main/asciidoc/object-mapping.adoc @@ -17,12 +17,13 @@ This means we need two fundamental steps: Spring Data automatically tries to detect a persistent entity's constructor to be used to materialize objects of that type. The resolution algorithm works as follows: -1. If there is a single constructor, it is used. -2. If there are multiple constructors and exactly one is annotated with `@PersistenceConstructor`, it is used. -3. If there's a no-argument constructor, it is used. +1. If there is a single static factory method annotated with `@FactoryMethod` then it is used. +2. If there is a single constructor, it is used. +3. If there are multiple constructors and exactly one is annotated with `@PersistenceConstructor`, it is used. +4. If there's a no-argument constructor, it is used. Other constructors will be ignored. -The value resolution assumes constructor argument names to match the property names of the entity, i.e. the resolution will be performed as if the property was to be populated, including all customizations in mapping (different datastore column or field name etc.). +The value resolution assumes constructor/factory method argument names to match the property names of the entity, i.e. the resolution will be performed as if the property was to be populated, including all customizations in mapping (different datastore column or field name etc.). This also requires either parameter names information available in the class file or an `@ConstructorProperties` annotation being present on the constructor. The value resolution can be customized by using Spring Framework's `@Value` value annotation using a store-specific SpEL expression. @@ -206,6 +207,7 @@ Even if the intent is that the calculation should be preferred, it's important t <6> The class exposes a factory method and a constructor for object creation. The core idea here is to use factory methods instead of additional constructors to avoid the need for constructor disambiguation through `@PersistenceConstructor`. Instead, defaulting of properties is handled within the factory method. +If you want Spring Data to use the factory method for object instantiation, annotate it with `@FactoryMethod`. [[mapping.general-recommendations]] == General recommendations diff --git a/src/main/java/org/springframework/data/annotation/EntityCreatorAnnotation.java b/src/main/java/org/springframework/data/annotation/EntityCreatorAnnotation.java new file mode 100644 index 000000000..d1132955c --- /dev/null +++ b/src/main/java/org/springframework/data/annotation/EntityCreatorAnnotation.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011-2021 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 + * + * https://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 org.springframework.data.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marker annotation to declare a constructor or factory method annotation as factory/preferred constructor annotation. + * + * @author Mark Paluch + * @since 3.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.ANNOTATION_TYPE }) +public @interface EntityCreatorAnnotation { +} diff --git a/src/main/java/org/springframework/data/annotation/FactoryMethod.java b/src/main/java/org/springframework/data/annotation/FactoryMethod.java new file mode 100644 index 000000000..9bccfe565 --- /dev/null +++ b/src/main/java/org/springframework/data/annotation/FactoryMethod.java @@ -0,0 +1,33 @@ +/* + * Copyright 2011-2021 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 + * + * https://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 org.springframework.data.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to declare a {@code static} method as factory method for class instantiation. + * + * @author Mark Paluch + * @since 3.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +@EntityCreatorAnnotation +public @interface FactoryMethod { +} diff --git a/src/main/java/org/springframework/data/annotation/PersistenceConstructor.java b/src/main/java/org/springframework/data/annotation/PersistenceConstructor.java index ae2cf26ba..a44afb790 100644 --- a/src/main/java/org/springframework/data/annotation/PersistenceConstructor.java +++ b/src/main/java/org/springframework/data/annotation/PersistenceConstructor.java @@ -21,9 +21,13 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** + * Annotation to declare a constructor for instantiation. + * * @author Jon Brisbin + * @author Mark Paluch */ @Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.CONSTRUCTOR) +@Target({ ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE }) +@EntityCreatorAnnotation public @interface PersistenceConstructor { } diff --git a/src/main/java/org/springframework/data/mapping/EntityCreatorMetadata.java b/src/main/java/org/springframework/data/mapping/EntityCreatorMetadata.java new file mode 100644 index 000000000..bdb3a41b0 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/EntityCreatorMetadata.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021 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 + * + * https://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 org.springframework.data.mapping; + +import java.util.List; + +/** + * Metadata describing a mechanism to create an entity instance. + * + * @author Mark Paluch + * @since 3.0 + */ +public interface EntityCreatorMetadata

> { + + /** + * Check whether the given {@link PersistentProperty} is being used as creator parameter. + * + * @param property + * @return + */ + boolean isCreatorParameter(PersistentProperty property); + + /** + * Returns whether the given {@link Parameter} is one referring to parent value (such as an enclosing class or a + * receiver parameter). + * + * @param parameter + * @return + */ + default boolean isParentParameter(Parameter parameter) { + return false; + } + + /** + * @return the number of parameters. + */ + default int getParameterCount() { + return getParameters().size(); + } + + /** + * @return the parameters used by this creator. + */ + List> getParameters(); + + /** + * @return whether the creator accepts {@link Parameter}s. + */ + default boolean hasParameters() { + return !getParameters().isEmpty(); + } +} diff --git a/src/main/java/org/springframework/data/mapping/EntityCreatorMetadataSupport.java b/src/main/java/org/springframework/data/mapping/EntityCreatorMetadataSupport.java new file mode 100644 index 000000000..c4fb4697f --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/EntityCreatorMetadataSupport.java @@ -0,0 +1,117 @@ +/* + * Copyright 2022 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 + * + * https://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 org.springframework.data.mapping; + +import java.lang.reflect.Executable; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.util.Assert; + +/** + * Value object to encapsulate the entity creation mechanism through a {@link Executable} to be used when mapping + * persistent data to objects. + * + * @author Mark Paluch + * @since 3.0 + */ +class EntityCreatorMetadataSupport> implements EntityCreatorMetadata

{ + + private final Executable executable; + private final List> parameters; + private final Map, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>(); + + /** + * Creates a new {@link EntityCreatorMetadataSupport} from the given {@link Executable} and {@link Parameter}s. + * + * @param executable must not be {@literal null}. + * @param parameters must not be {@literal null}. + */ + @SafeVarargs + public EntityCreatorMetadataSupport(Executable executable, Parameter... parameters) { + + Assert.notNull(executable, "Executable must not be null!"); + Assert.notNull(parameters, "Parameters must not be null!"); + + this.executable = executable; + this.parameters = Arrays.asList(parameters); + } + + /** + * Returns the underlying {@link Executable} that can be invoked reflectively. + * + * @return + */ + Executable getExecutable() { + return executable; + } + + /** + * Returns the {@link Parameter}s of the executable. + * + * @return + */ + public List> getParameters() { + return parameters; + } + + /** + * Returns whether the given {@link PersistentProperty} is referenced in a creator argument of the + * {@link PersistentEntity} backing this {@link EntityCreatorMetadataSupport}. + *

+ * Results of this call are cached and reused on the next invocation. Calling this method for a + * {@link PersistentProperty} that was not yet added to its owning {@link PersistentEntity} will capture that state + * and return the same result after adding {@link PersistentProperty} to its entity. + * + * @param property must not be {@literal null}. + * @return {@literal true} if the {@link PersistentProperty} is used in the creator. + */ + @Override + public boolean isCreatorParameter(PersistentProperty property) { + + Assert.notNull(property, "Property must not be null!"); + + Boolean cached = isPropertyParameterCache.get(property); + + if (cached != null) { + return cached; + } + + boolean result = doGetIsCreatorParameter(property); + + isPropertyParameterCache.put(property, result); + + return result; + } + + @Override + public String toString() { + return executable.toString(); + } + + private boolean doGetIsCreatorParameter(PersistentProperty property) { + + for (Parameter parameter : parameters) { + if (parameter.maps(property)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/org/springframework/data/mapping/FactoryMethod.java b/src/main/java/org/springframework/data/mapping/FactoryMethod.java new file mode 100644 index 000000000..f636f7bd3 --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/FactoryMethod.java @@ -0,0 +1,53 @@ +/* + * Copyright 2011-2021 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 + * + * https://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 org.springframework.data.mapping; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import org.springframework.util.ReflectionUtils; + +/** + * Value object to encapsulate the factory method to be used when mapping persistent data to objects. + * + * @author Mark Paluch + * @since 3.0 + */ +public final class FactoryMethod> extends EntityCreatorMetadataSupport { + + /** + * Creates a new {@link FactoryMethod} from the given {@link Constructor} and {@link Parameter}s. + * + * @param factoryMethod must not be {@literal null}. + * @param parameters must not be {@literal null}. + */ + @SafeVarargs + public FactoryMethod(Method factoryMethod, Parameter... parameters) { + + super(factoryMethod, parameters); + ReflectionUtils.makeAccessible(factoryMethod); + } + + /** + * Returns the underlying {@link Constructor}. + * + * @return + */ + public Method getFactoryMethod() { + return (Method) getExecutable(); + } + +} diff --git a/src/main/java/org/springframework/data/mapping/Parameter.java b/src/main/java/org/springframework/data/mapping/Parameter.java new file mode 100644 index 000000000..ebcd605de --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/Parameter.java @@ -0,0 +1,213 @@ +/* + * Copyright 2021 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 + * + * https://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 org.springframework.data.mapping; + +import java.lang.annotation.Annotation; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.data.util.Lazy; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +/** + * Value object to represent constructor parameters. + * + * @param the type of the parameter + * @author Oliver Gierke + */ +public class Parameter> { + + private final @Nullable String name; + private final TypeInformation type; + private final MergedAnnotations annotations; + private final String key; + private final @Nullable PersistentEntity entity; + + private final Lazy enclosingClassCache; + private final Lazy hasSpelExpression; + + /** + * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of + * {@link Annotation}s. Will inspect the annotations for an {@link Value} annotation to lookup a key or an SpEL + * expression to be evaluated. + * + * @param name the name of the parameter, can be {@literal null} + * @param type must not be {@literal null} + * @param annotations must not be {@literal null} but can be empty + * @param entity must not be {@literal null}. + */ + public Parameter(@Nullable String name, TypeInformation type, Annotation[] annotations, + @Nullable PersistentEntity entity) { + + Assert.notNull(type, "Type must not be null!"); + Assert.notNull(annotations, "Annotations must not be null!"); + + this.name = name; + this.type = type; + this.annotations = MergedAnnotations.from(annotations); + this.key = getValue(this.annotations); + this.entity = entity; + + this.enclosingClassCache = Lazy.of(() -> { + + if (entity == null) { + throw new IllegalStateException(); + } + + Class owningType = entity.getType(); + + return owningType.isMemberClass() && type.getType().equals(owningType.getEnclosingClass()); + }); + + this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression())); + } + + @Nullable + private static String getValue(MergedAnnotations annotations) { + + return annotations.get(Value.class) // + .getValue("value", String.class) // + .filter(StringUtils::hasText) // + .orElse(null); + } + + /** + * Returns the name of the parameter. + * + * @return + */ + @Nullable + public String getName() { + return name; + } + + /** + * Returns the {@link TypeInformation} of the parameter. + * + * @return + */ + public TypeInformation getType() { + return type; + } + + /** + * Merged annotations that this parameter is annotated with. + * + * @return + * @since 2.5 + */ + public MergedAnnotations getAnnotations() { + return annotations; + } + + /** + * Returns the raw resolved type of the parameter. + * + * @return + */ + public Class getRawType() { + return type.getType(); + } + + /** + * Returns the key to be used when looking up a source data structure to populate the actual parameter value. + * + * @return + */ + public String getSpelExpression() { + return key; + } + + /** + * Returns whether the constructor parameter is equipped with a SpEL expression. + * + * @return + */ + public boolean hasSpelExpression() { + return this.hasSpelExpression.get(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + + if (this == o) { + return true; + } + + if (!(o instanceof Parameter)) { + return false; + } + + Parameter parameter = (Parameter) o; + + if (!ObjectUtils.nullSafeEquals(name, parameter.name)) { + return false; + } + + if (!ObjectUtils.nullSafeEquals(type, parameter.type)) { + return false; + } + + if (!ObjectUtils.nullSafeEquals(key, parameter.key)) { + return false; + } + + return ObjectUtils.nullSafeEquals(entity, parameter.entity); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + int result = ObjectUtils.nullSafeHashCode(name); + result = 31 * result + ObjectUtils.nullSafeHashCode(type); + result = 31 * result + ObjectUtils.nullSafeHashCode(key); + result = 31 * result + ObjectUtils.nullSafeHashCode(entity); + return result; + } + + /** + * Returns whether the {@link Parameter} maps the given {@link PersistentProperty}. + * + * @param property + * @return + */ + boolean maps(PersistentProperty property) { + + PersistentEntity entity = this.entity; + String name = this.name; + + PersistentProperty referencedProperty = entity == null // + ? null // + : name == null ? null : entity.getPersistentProperty(name); + + return property.equals(referencedProperty); + } + + boolean isEnclosingClassParameter() { + return enclosingClassCache.get(); + } +} diff --git a/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/src/main/java/org/springframework/data/mapping/PersistentEntity.java index 833ee40b7..11d9de611 100644 --- a/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -49,10 +49,24 @@ public interface PersistentEntity> extends It * indicates that the instantiation of the object of that persistent entity is done through either a customer * {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion * mechanisms entirely. + * @deprecated since 3.0, use {@link #getEntityCreator()}. */ @Nullable + @Deprecated PreferredConstructor getPersistenceConstructor(); + /** + * Returns the {@link EntityCreatorMetadata} to be used to instantiate objects of this {@link PersistentEntity}. + * + * @return {@literal null} in case no suitable creation mechanism for automatic construction can be found. This + * usually indicates that the instantiation of the object of that persistent entity is done through either a + * customer {@link org.springframework.data.mapping.model.EntityInstantiator} or handled by custom conversion + * mechanisms entirely. + * @since 3.0 + */ + @Nullable + EntityCreatorMetadata

getEntityCreator(); + /** * Returns whether the given {@link PersistentProperty} is referred to by a constructor argument of the * {@link PersistentEntity}. @@ -60,8 +74,22 @@ public interface PersistentEntity> extends It * @param property can be {@literal null}. * @return true if the given {@link PersistentProperty} is referred to by a constructor argument or {@literal false} * if not or {@literal null}. + * @deprecated since 3.0, use {@link #isCreatorArgument(PersistentProperty)} instead. */ - boolean isConstructorArgument(PersistentProperty property); + @Deprecated + default boolean isConstructorArgument(PersistentProperty property) { + return isCreatorArgument(property); + } + + /** + * Returns whether the given {@link PersistentProperty} is referred to by a creator argument of the + * {@link PersistentEntity}. + * + * @param property can be {@literal null}. + * @return true if the given {@link PersistentProperty} is referred to by a creator argument or {@literal false} if + * not or {@literal null}. + */ + boolean isCreatorArgument(PersistentProperty property); /** * Returns whether the given {@link PersistentProperty} is the id property of the entity. diff --git a/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index 8ac155a56..4ef733003 100644 --- a/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -15,25 +15,14 @@ */ package org.springframework.data.mapping; -import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.convert.WritingConverter; -import org.springframework.data.util.Lazy; -import org.springframework.data.util.TypeInformation; -import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; /** * Value object to encapsulate the constructor to be used when mapping persistent data to objects. @@ -46,11 +35,9 @@ import org.springframework.util.StringUtils; * @author Myeonghyeon Lee * @author Xeno Amess */ -public class PreferredConstructor> { +public final class PreferredConstructor> extends EntityCreatorMetadataSupport { - private final Constructor constructor; private final List> parameters; - private final Map, Boolean> isPropertyParameterCache = new ConcurrentHashMap<>(); /** * Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s. @@ -61,11 +48,9 @@ public class PreferredConstructor> { @SafeVarargs public PreferredConstructor(Constructor constructor, Parameter... parameters) { - Assert.notNull(constructor, "Constructor must not be null!"); - Assert.notNull(parameters, "Parameters must not be null!"); + super(constructor, parameters); ReflectionUtils.makeAccessible(constructor); - this.constructor = constructor; this.parameters = Arrays.asList(parameters); } @@ -75,27 +60,9 @@ public class PreferredConstructor> { * @return */ public Constructor getConstructor() { - return constructor; + return (Constructor) getExecutable(); } - /** - * Returns the {@link Parameter}s of the constructor. - * - * @return - */ - public List> getParameters() { - return parameters; - } - - /** - * Returns whether the constructor has {@link Parameter}s. - * - * @see #isNoArgConstructor() - * @return - */ - public boolean hasParameters() { - return !parameters.isEmpty(); - } /** * Returns whether the constructor does not have any arguments. @@ -104,7 +71,7 @@ public class PreferredConstructor> { * @return */ public boolean isNoArgConstructor() { - return parameters.isEmpty(); + return !hasParameters(); } /** @@ -113,41 +80,22 @@ public class PreferredConstructor> { * @return */ public boolean isExplicitlyAnnotated() { - return AnnotationUtils.findAnnotation(constructor, PersistenceConstructor.class) != null; + return MergedAnnotations.from(getExecutable()).isPresent(PersistenceConstructor.class); } /** - * Returns whether the given {@link PersistentProperty} is referenced in a constructor argument of the - * {@link PersistentEntity} backing this {@link PreferredConstructor}. - *

- * Results of this call are cached and reused on the next invocation. Calling this method for a - * {@link PersistentProperty} that was not yet added to its owning {@link PersistentEntity} will capture that state - * and return the same result after adding {@link PersistentProperty} to its entity. - * - * @param property must not be {@literal null}. - * @return {@literal true} if the {@link PersistentProperty} is used in the constructor. + * @param property + * @return + * @deprecated since 3.0, use {@link #isCreatorParameter(PersistentProperty)} instead. */ + @Deprecated public boolean isConstructorParameter(PersistentProperty property) { + return isCreatorParameter(property); + } + @Override - Assert.notNull(property, "Property must not be null!"); - - Boolean cached = isPropertyParameterCache.get(property); - - if (cached != null) { - return cached; - } - - boolean result = false; - for (Parameter parameter : parameters) { - if (parameter.maps(property)) { - result = true; - break; - } - } - - isPropertyParameterCache.put(property, result); - - return result; + public boolean isParentParameter(Parameter parameter) { + return isEnclosingClassParameter(parameter); } /** @@ -169,186 +117,4 @@ public class PreferredConstructor> { return parameters.get(0).equals(parameter); } - /** - * Value object to represent constructor parameters. - * - * @param the type of the parameter - * @author Oliver Gierke - */ - public static class Parameter> { - - private final @Nullable String name; - private final TypeInformation type; - private final MergedAnnotations annotations; - private final String key; - private final @Nullable PersistentEntity entity; - - private final Lazy enclosingClassCache; - private final Lazy hasSpelExpression; - - /** - * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of - * {@link Annotation}s. Will inspect the annotations for an {@link Value} annotation to lookup a key or an SpEL - * expression to be evaluated. - * - * @param name the name of the parameter, can be {@literal null} - * @param type must not be {@literal null} - * @param annotations must not be {@literal null} but can be empty - * @param entity must not be {@literal null}. - */ - public Parameter(@Nullable String name, TypeInformation type, Annotation[] annotations, - @Nullable PersistentEntity entity) { - - Assert.notNull(type, "Type must not be null!"); - Assert.notNull(annotations, "Annotations must not be null!"); - - this.name = name; - this.type = type; - this.annotations = MergedAnnotations.from(annotations); - this.key = getValue(this.annotations); - this.entity = entity; - - this.enclosingClassCache = Lazy.of(() -> { - - if (entity == null) { - throw new IllegalStateException(); - } - - Class owningType = entity.getType(); - return owningType.isMemberClass() && type.getType().equals(owningType.getEnclosingClass()); - }); - - this.hasSpelExpression = Lazy.of(() -> StringUtils.hasText(getSpelExpression())); - } - - @Nullable - private static String getValue(MergedAnnotations annotations) { - - return annotations.get(Value.class) // - .getValue("value", String.class) // - .filter(StringUtils::hasText) // - .orElse(null); - } - - /** - * Returns the name of the parameter. - * - * @return - */ - @Nullable - public String getName() { - return name; - } - - /** - * Returns the {@link TypeInformation} of the parameter. - * - * @return - */ - public TypeInformation getType() { - return type; - } - - /** - * Merged annotations that this parameter is annotated with. - * - * @return - * @since 2.5 - */ - public MergedAnnotations getAnnotations() { - return annotations; - } - - /** - * Returns the raw resolved type of the parameter. - * - * @return - */ - public Class getRawType() { - return type.getType(); - } - - /** - * Returns the key to be used when looking up a source data structure to populate the actual parameter value. - * - * @return - */ - public String getSpelExpression() { - return key; - } - - /** - * Returns whether the constructor parameter is equipped with a SpEL expression. - * - * @return - */ - public boolean hasSpelExpression() { - return this.hasSpelExpression.get(); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object o) { - - if (this == o) { - return true; - } - - if (!(o instanceof Parameter)) { - return false; - } - - Parameter parameter = (Parameter) o; - - if (!ObjectUtils.nullSafeEquals(name, parameter.name)) { - return false; - } - - if (!ObjectUtils.nullSafeEquals(type, parameter.type)) { - return false; - } - - if (!ObjectUtils.nullSafeEquals(key, parameter.key)) { - return false; - } - - return ObjectUtils.nullSafeEquals(entity, parameter.entity); - } - - /* - * (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - int result = ObjectUtils.nullSafeHashCode(name); - result = 31 * result + ObjectUtils.nullSafeHashCode(type); - result = 31 * result + ObjectUtils.nullSafeHashCode(key); - result = 31 * result + ObjectUtils.nullSafeHashCode(entity); - return result; - } - - /** - * Returns whether the {@link Parameter} maps the given {@link PersistentProperty}. - * - * @param property - * @return - */ - boolean maps(PersistentProperty property) { - - PersistentEntity entity = this.entity; - String name = this.name; - - P referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name); - - return property.equals(referencedProperty); - } - - private boolean isEnclosingClassParameter() { - return enclosingClassCache.get(); - } - } } diff --git a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index cd47b6a77..da7df78e6 100644 --- a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -63,7 +63,7 @@ public class BasicPersistentEntity> implement private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!"; - private final @Nullable PreferredConstructor constructor; + private final @Nullable EntityCreatorMetadata

creator; private final TypeInformation information; private final List

properties; private final List

persistentPropertiesCache; @@ -109,7 +109,7 @@ public class BasicPersistentEntity> implement this.properties = new ArrayList<>(); this.persistentPropertiesCache = new ArrayList<>(); this.comparator = comparator; - this.constructor = PreferredConstructorDiscoverer.discover(this); + this.creator = EntityCreatorMetadataDiscoverer.discover(this); this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator)); this.propertyCache = new HashMap<>(16, 1f); @@ -124,7 +124,7 @@ public class BasicPersistentEntity> implement this.isImmutable = Lazy.of(() -> isAnnotationPresent(Immutable.class)); this.requiresPropertyPopulation = Lazy.of(() -> !isImmutable() && properties.stream() // - .anyMatch(it -> !(isConstructorArgument(it) || it.isTransient()))); + .anyMatch(it -> !(isCreatorArgument(it) || it.isTransient()))); } /* @@ -132,16 +132,23 @@ public class BasicPersistentEntity> implement * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor() */ @Nullable + @Override public PreferredConstructor getPersistenceConstructor() { - return constructor; + return creator instanceof PreferredConstructor ? (PreferredConstructor) creator : null; + } + + @Override + public EntityCreatorMetadata

getEntityCreator() { + return creator; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty) */ - public boolean isConstructorArgument(PersistentProperty property) { - return constructor != null && constructor.isConstructorParameter(property); + @Override + public boolean isCreatorArgument(PersistentProperty property) { + return creator != null && creator.isCreatorParameter(property); } /* diff --git a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java index 11b04c496..1e4d399ae 100644 --- a/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiator.java @@ -18,6 +18,7 @@ package org.springframework.data.mapping.model; import static org.springframework.asm.Opcodes.*; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; @@ -26,7 +27,6 @@ import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.asm.ClassWriter; import org.springframework.asm.MethodVisitor; import org.springframework.asm.Opcodes; @@ -34,19 +34,22 @@ import org.springframework.asm.Type; import org.springframework.beans.BeanInstantiationException; import org.springframework.cglib.core.ReflectUtils; import org.springframework.core.NativeDetector; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.FactoryMethod; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import org.springframework.util.ClassUtils; /** * An {@link EntityInstantiator} that can generate byte code to speed-up dynamic object instantiation. Uses the * {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an instance of the entity by dynamically * generating factory methods with appropriate constructor invocations via ASM. If we cannot generate byte code for a - * type, we gracefully fall-back to the {@link ReflectionEntityInstantiator}. + * type, we gracefully fallback to the {@link ReflectionEntityInstantiator}. * * @author Thomas Darimont * @author Oliver Gierke @@ -65,11 +68,21 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { private volatile Map, EntityInstantiator> entityInstantiators = new HashMap<>(32); + private final boolean fallbackToReflectionOnError; + /** * Creates a new {@link ClassGeneratingEntityInstantiator}. */ public ClassGeneratingEntityInstantiator() { + this(true); + } + + /** + * Creates a new {@link ClassGeneratingEntityInstantiator}. + */ + ClassGeneratingEntityInstantiator(boolean fallbackToReflectionOnError) { this.generator = new ObjectInstantiatorClassGenerator(); + this.fallbackToReflectionOnError = fallbackToReflectionOnError; } /* @@ -127,18 +140,22 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { return MappingInstantiationExceptionEntityInstantiator.create(entity.getType()); } - try { - return doCreateEntityInstantiator(entity); - } catch (Throwable ex) { + if (fallbackToReflectionOnError) { + try { + return doCreateEntityInstantiator(entity); + } catch (Throwable ex) { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug( - String.format("Cannot create entity instantiator for %s. Falling back to ReflectionEntityInstantiator.", - entity.getName()), - ex); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + String.format("Cannot create entity instantiator for %s. Falling back to ReflectionEntityInstantiator.", + entity.getName()), + ex); + } + return ReflectionEntityInstantiator.INSTANCE; } - return ReflectionEntityInstantiator.INSTANCE; } + + return doCreateEntityInstantiator(entity); } /** @@ -146,7 +163,8 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * @return */ protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { - return new EntityInstantiatorAdapter(createObjectInstantiator(entity, entity.getPersistenceConstructor())); + return new EntityInstantiatorAdapter( + createObjectInstantiator(entity, entity.getEntityCreator())); } /** @@ -174,11 +192,30 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { return true; } - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); - if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) { + EntityCreatorMetadata entityCreator = entity.getEntityCreator(); + + if (entityCreator == null) { return true; } + if (entityCreator instanceof PreferredConstructor) { + + PreferredConstructor persistenceConstructor = (PreferredConstructor) entityCreator; + + if (Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) { + return true; + } + } + + if (entityCreator instanceof FactoryMethod) { + + FactoryMethod factoryMethod = (FactoryMethod) entityCreator; + + if (Modifier.isPrivate(factoryMethod.getFactoryMethod().getModifiers())) { + return true; + } + } + if (!ClassUtils.isPresent(ObjectInstantiator.class.getName(), type.getClassLoader())) { return true; } @@ -199,7 +236,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { /** * Creates a dynamically generated {@link ObjectInstantiator} for the given {@link PersistentEntity} and - * {@link PreferredConstructor}. There will always be exactly one {@link ObjectInstantiator} instance per + * {@link EntityCreatorMetadata}. There will always be exactly one {@link ObjectInstantiator} instance per * {@link PersistentEntity}. * * @param entity @@ -207,7 +244,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * @return */ ObjectInstantiator createObjectInstantiator(PersistentEntity entity, - @Nullable PreferredConstructor constructor) { + @Nullable EntityCreatorMetadata constructor) { try { return (ObjectInstantiator) this.generator.generateCustomInstantiatorClass(entity, constructor).newInstance(); @@ -245,7 +282,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); + Object[] params = extractInvocationArguments(entity.getEntityCreator(), provider); try { return (T) instantiator.newInstance(params); @@ -263,13 +300,13 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * @return */ static

, T> Object[] extractInvocationArguments( - @Nullable PreferredConstructor constructor, ParameterValueProvider

provider) { + @Nullable EntityCreatorMetadata

constructor, ParameterValueProvider

provider) { if (constructor == null || !constructor.hasParameters()) { return allocateArguments(0); } - Object[] params = allocateArguments(constructor.getConstructor().getParameterCount()); + Object[] params = allocateArguments(constructor.getParameterCount()); int index = 0; for (Parameter parameter : constructor.getParameters()) { @@ -316,7 +353,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); + Object[] params = extractInvocationArguments(entity.getEntityCreator(), provider); throw new MappingInstantiationException(entity, Arrays.asList(params), new BeanInstantiationException(typeToCreate, "Class is abstract")); @@ -366,7 +403,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { private static final String INIT = ""; private static final String TAG = "_Instantiator_"; - private static final String JAVA_LANG_OBJECT = "java/lang/Object"; + private static final String JAVA_LANG_OBJECT = Type.getInternalName(Object.class); private static final String CREATE_METHOD_NAME = "newInstance"; private static final String[] IMPLEMENTED_INTERFACES = new String[] { @@ -380,7 +417,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * @return */ public Class generateCustomInstantiatorClass(PersistentEntity entity, - @Nullable PreferredConstructor constructor) { + @Nullable EntityCreatorMetadata constructor) { String className = generateClassName(entity); Class type = entity.getType(); @@ -417,11 +454,11 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * * @param internalClassName * @param entity - * @param constructor + * @param entityCreator * @return */ public byte[] generateBytecode(String internalClassName, PersistentEntity entity, - @Nullable PreferredConstructor constructor) { + @Nullable EntityCreatorMetadata entityCreator) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); @@ -430,7 +467,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { visitDefaultConstructor(cw); - visitCreateMethod(cw, entity, constructor); + visitCreateMethod(cw, entity, entityCreator); cw.visitEnd(); @@ -453,49 +490,79 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { * * @param cw * @param entity - * @param constructor + * @param entityCreator */ private void visitCreateMethod(ClassWriter cw, PersistentEntity entity, - @Nullable PreferredConstructor constructor) { + @Nullable EntityCreatorMetadata entityCreator) { String entityTypeResourcePath = Type.getInternalName(entity.getType()); MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, CREATE_METHOD_NAME, - "([Ljava/lang/Object;)Ljava/lang/Object;", null, null); + "([" + BytecodeUtil.referenceName(Object.class) + ")" + BytecodeUtil.referenceName(Object.class), + null, null); mv.visitCode(); mv.visitTypeInsn(NEW, entityTypeResourcePath); mv.visitInsn(DUP); - if (constructor != null) { + if (entityCreator instanceof PreferredConstructor) { + visitConstructorCreation((PreferredConstructor) entityCreator, mv, entityTypeResourcePath); + } - Constructor ctor = constructor.getConstructor(); - Class[] parameterTypes = ctor.getParameterTypes(); - List> parameters = constructor.getParameters(); + if (entityCreator instanceof FactoryMethod) { + visitFactoryMethodCreation((FactoryMethod) entityCreator, mv, entityTypeResourcePath); + } - for (int i = 0; i < parameterTypes.length; i++) { + mv.visitInsn(ARETURN); + mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS + mv.visitEnd(); + } - mv.visitVarInsn(ALOAD, 1); + private static void visitConstructorCreation(PreferredConstructor constructor, MethodVisitor mv, + String entityTypeResourcePath) { - visitArrayIndex(mv, i); + Constructor ctor = constructor.getConstructor(); + Class[] parameterTypes = ctor.getParameterTypes(); + List> parameters = constructor.getParameters(); - mv.visitInsn(AALOAD); + visitParameterTypes(mv, parameterTypes, parameters); - if (parameterTypes[i].isPrimitive()) { + mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, INIT, Type.getConstructorDescriptor(ctor), false); + } - mv.visitInsn(DUP); - String parameterName = parameters.size() > i ? parameters.get(i).getName() : null; + private static void visitFactoryMethodCreation(FactoryMethod factoryMethod, MethodVisitor mv, + String entityTypeResourcePath) { - insertAssertNotNull(mv, parameterName == null ? String.format("at index %d", i) : parameterName); - insertUnboxInsns(mv, Type.getType(parameterTypes[i]).toString().charAt(0), ""); - } else { - mv.visitTypeInsn(CHECKCAST, Type.getInternalName(parameterTypes[i])); - } + Method method = factoryMethod.getFactoryMethod(); + Class[] parameterTypes = method.getParameterTypes(); + List> parameters = factoryMethod.getParameters(); + + visitParameterTypes(mv, parameterTypes, parameters); + + mv.visitMethodInsn(INVOKESTATIC, entityTypeResourcePath, method.getName(), Type.getMethodDescriptor(method), + false); + } + + private static void visitParameterTypes(MethodVisitor mv, Class[] parameterTypes, + List> parameters) { + + for (int i = 0; i < parameterTypes.length; i++) { + + mv.visitVarInsn(ALOAD, 1); + + visitArrayIndex(mv, i); + + mv.visitInsn(AALOAD); + + if (parameterTypes[i].isPrimitive()) { + + mv.visitInsn(DUP); + String parameterName = parameters.size() > i ? parameters.get(i).getName() : null; + + insertAssertNotNull(mv, parameterName == null ? String.format("at index %d", i) : parameterName); + insertUnboxInsns(mv, Type.getType(parameterTypes[i]).toString().charAt(0), ""); + } else { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(parameterTypes[i])); } - - mv.visitMethodInsn(INVOKESPECIAL, entityTypeResourcePath, INIT, Type.getConstructorDescriptor(ctor), false); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); // (0, 0) = computed via ClassWriter.COMPUTE_MAXS - mv.visitEnd(); } } @@ -525,8 +592,8 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { // Assert.notNull(property) mv.visitLdcInsn(String.format("Parameter %s must not be null!", parameterName)); - mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull", - String.format("(%s%s)V", String.format("L%s;", JAVA_LANG_OBJECT), "Ljava/lang/String;"), false); + mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Assert.class), "notNull", String.format("(%s%s)V", + BytecodeUtil.referenceName(JAVA_LANG_OBJECT), BytecodeUtil.referenceName(String.class)), false); } /** @@ -542,52 +609,52 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator { switch (ch) { case 'Z': - if (!stackDescriptor.equals("Ljava/lang/Boolean")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Boolean.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Boolean.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Boolean.class), "booleanValue", "()Z", false); break; case 'B': - if (!stackDescriptor.equals("Ljava/lang/Byte")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Byte.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Byte.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Byte.class), "byteValue", "()B", false); break; case 'C': - if (!stackDescriptor.equals("Ljava/lang/Character")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Character.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Character.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Character.class), "charValue", "()C", false); break; case 'D': - if (!stackDescriptor.equals("Ljava/lang/Double")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Double.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Double.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Double.class), "doubleValue", "()D", false); break; case 'F': - if (!stackDescriptor.equals("Ljava/lang/Float")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Float.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Float.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Float.class), "floatValue", "()F", false); break; case 'I': - if (!stackDescriptor.equals("Ljava/lang/Integer")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Integer.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Integer.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Integer.class), "intValue", "()I", false); break; case 'J': - if (!stackDescriptor.equals("Ljava/lang/Long")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Long.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Long.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Long.class), "longValue", "()J", false); break; case 'S': - if (!stackDescriptor.equals("Ljava/lang/Short")) { - mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); + if (!stackDescriptor.equals(BytecodeUtil.referenceName(Short.class))) { + mv.visitTypeInsn(CHECKCAST, Type.getInternalName(Short.class)); } - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); + mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(Short.class), "shortValue", "()S", false); break; default: throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); diff --git a/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java b/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java index 4bc62922f..2e68450e3 100644 --- a/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java +++ b/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java @@ -16,7 +16,7 @@ package org.springframework.data.mapping.model; -import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.Parameter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; diff --git a/src/main/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscoverer.java b/src/main/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscoverer.java new file mode 100644 index 000000000..537e5f6be --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscoverer.java @@ -0,0 +1,164 @@ +/* + * Copyright 2021 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 + * + * https://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 org.springframework.data.mapping.model; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.core.DefaultParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.data.annotation.EntityCreatorAnnotation; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.FactoryMethod; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.Parameter; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.util.TypeInformation; +import org.springframework.lang.Nullable; + +/** + * Discoverer for factory methods and persistence constructors. + * + * @author Mark Paluch + * @since 3.0 + */ +class EntityCreatorMetadataDiscoverer { + + private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); + + /** + * Discover an entity creator + * + * @param entity + * @param + * @param

+ * @return + */ + @Nullable + public static > EntityCreatorMetadata

discover(PersistentEntity entity) { + + Constructor[] declaredConstructors = entity.getType().getDeclaredConstructors(); + Method[] declaredMethods = entity.getType().getDeclaredMethods(); + + boolean hasAnnotatedFactoryMethod = findAnnotation(EntityCreatorAnnotation.class, declaredMethods); + boolean hasAnnotatedConstructor = findAnnotation(EntityCreatorAnnotation.class, declaredConstructors); + + if (hasAnnotatedConstructor && hasAnnotatedFactoryMethod) { + throw new MappingException( + String.format( + "Invalid usage of @Factory and @PersistenceConstructor on %s. Only one annotation type permitted to indicate how entity instances should be created.", + entity.getType().getName())); + } + + if (hasAnnotatedFactoryMethod) { + + List candidates = discoverFactoryMethods(entity, declaredMethods); + + if (candidates.size() == 1) { + return getFactoryMethod(entity, candidates.get(0)); + } + } + + return PreferredConstructorDiscoverer.discover(entity); + } + + private static > List discoverFactoryMethods(PersistentEntity entity, + Method[] declaredMethods) { + + List candidates = new ArrayList<>(); + + for (Method method : declaredMethods) { + + validateMethod(method); + + if (!isFactoryMethod(method, entity.getType())) { + continue; + } + + if (findAnnotation(EntityCreatorAnnotation.class, method)) { + candidates.add(method); + } + } + + return candidates; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static > FactoryMethod getFactoryMethod( + PersistentEntity entity, Method method) { + + Parameter[] parameters = new Parameter[method.getParameterCount()]; + Annotation[][] parameterAnnotations = method.getParameterAnnotations(); + List> types = entity.getTypeInformation().getParameterTypes(method); + + String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method); + + for (int i = 0; i < parameters.length; i++) { + + String name = parameterNames == null || parameterNames.length <= i ? null : parameterNames[i]; + TypeInformation type = types.get(i); + Annotation[] annotations = parameterAnnotations[i]; + + parameters[i] = new Parameter(name, type, annotations, entity); + } + + return new FactoryMethod<>(method, parameters); + } + + private static void validateMethod(Method method) { + + if (MergedAnnotations.from(method).isPresent(EntityCreatorAnnotation.class)) { + + if (!Modifier.isStatic(method.getModifiers())) { + throw new MappingException( + String.format("@Factory can only be used on static methods. Offending method: %s", method)); + } + } + } + + private static boolean isFactoryMethod(Method method, Class type) { + + // private methods not supported + if (Modifier.isPrivate(method.getModifiers())) { + return false; + } + + // synthetic methods not supported + if (method.isSynthetic()) { + return false; + } + + return Modifier.isStatic(method.getModifiers()) && method.getReturnType().isAssignableFrom(type); + } + + private static boolean findAnnotation(Class annotationType, AnnotatedElement... elements) { + + for (AnnotatedElement element : elements) { + if (MergedAnnotations.from(element).isPresent(annotationType)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java index 014e9597f..3a9830fdc 100644 --- a/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java +++ b/src/main/java/org/springframework/data/mapping/model/InstantiationAwarePropertyAccessor.java @@ -19,11 +19,11 @@ import java.util.function.Function; import org.springframework.core.KotlinDetector; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -107,22 +107,22 @@ public class InstantiationAwarePropertyAccessor implements PersistentProperty return; } - PreferredConstructor constructor = owner.getPersistenceConstructor(); + EntityCreatorMetadata creator = owner.getEntityCreator(); - if (constructor == null) { + if (creator == null) { throw new IllegalStateException(String.format(NO_SETTER_OR_CONSTRUCTOR, property.getName(), owner.getType())); } - if (!constructor.isConstructorParameter(property)) { + if (!creator.isCreatorParameter(property)) { throw new IllegalStateException( - String.format(NO_CONSTRUCTOR_PARAMETER, property.getName(), constructor.getConstructor())); + String.format(NO_CONSTRUCTOR_PARAMETER, property.getName(), creator)); } - constructor.getParameters().forEach(it -> { + creator.getParameters().forEach(it -> { if (it.getName() == null) { throw new IllegalStateException( - String.format("Cannot detect parameter names of copy constructor of %s!", owner.getType())); + String.format("Cannot detect parameter names of copy creator of %s!", owner.getType())); } }); diff --git a/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java index a89e71143..fa3273054 100644 --- a/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java +++ b/src/main/java/org/springframework/data/mapping/model/KotlinClassGeneratingEntityInstantiator.java @@ -24,10 +24,11 @@ import java.util.Arrays; import java.util.List; import java.util.stream.IntStream; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.KotlinReflectionUtils; import org.springframework.data.util.ReflectionUtils; import org.springframework.lang.Nullable; @@ -49,9 +50,10 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta @Override protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity entity) { - PreferredConstructor constructor = entity.getPersistenceConstructor(); + EntityCreatorMetadata creator = entity.getEntityCreator(); - if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) { + if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType()) + && creator instanceof PreferredConstructor) { PreferredConstructor defaultConstructor = new DefaultingKotlinConstructorResolver(entity) .getDefaultConstructor(); @@ -60,7 +62,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta ObjectInstantiator instantiator = createObjectInstantiator(entity, defaultConstructor); - return new DefaultingKotlinClassInstantiatorAdapter(instantiator, constructor); + return new DefaultingKotlinClassInstantiatorAdapter(instantiator, (PreferredConstructor) creator); } } @@ -82,9 +84,12 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta DefaultingKotlinConstructorResolver(PersistentEntity entity) { Constructor hit = resolveDefaultConstructor(entity); - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + EntityCreatorMetadata creator = entity.getEntityCreator(); + + if (hit != null && creator instanceof PreferredConstructor) { + + PreferredConstructor persistenceConstructor = (PreferredConstructor) creator; - if (hit != null && persistenceConstructor != null) { this.defaultConstructor = new PreferredConstructor<>(hit, persistenceConstructor.getParameters().toArray(new Parameter[0])); } else { @@ -95,12 +100,14 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta @Nullable private static Constructor resolveDefaultConstructor(PersistentEntity entity) { - PreferredConstructor persistenceConstructor = entity.getPersistenceConstructor(); + EntityCreatorMetadata creator = entity.getEntityCreator(); - if (persistenceConstructor == null) { + if (!(creator instanceof PreferredConstructor)) { return null; } + PreferredConstructor persistenceConstructor = (PreferredConstructor) entity.getEntityCreator(); + Constructor hit = null; Constructor constructor = persistenceConstructor.getConstructor(); @@ -199,7 +206,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - Object[] params = extractInvocationArguments(entity.getPersistenceConstructor(), provider); + Object[] params = extractInvocationArguments(entity.getEntityCreator(), provider); try { return (T) instantiator.newInstance(params); @@ -209,17 +216,17 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta } private

, T> Object[] extractInvocationArguments( - @Nullable PreferredConstructor preferredConstructor, ParameterValueProvider

provider) { + @Nullable EntityCreatorMetadata

entityCreator, ParameterValueProvider

provider) { - if (preferredConstructor == null) { - throw new IllegalArgumentException("PreferredConstructor must not be null!"); + if (entityCreator == null) { + throw new IllegalArgumentException("EntityCreator must not be null!"); } Object[] params = allocateArguments(synthetic.getParameterCount() + KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1); int userParameterCount = kParameters.size(); - List> parameters = preferredConstructor.getParameters(); + List> parameters = entityCreator.getParameters(); // Prepare user-space arguments for (int i = 0; i < userParameterCount; i++) { diff --git a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java index 6ac1494d6..4791c2988 100644 --- a/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java +++ b/src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java @@ -19,10 +19,13 @@ import kotlin.reflect.KFunction; import kotlin.reflect.jvm.ReflectJvmMapping; import java.lang.reflect.Constructor; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.FactoryMethod; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.util.KotlinReflectionUtils; @@ -43,7 +46,7 @@ public class MappingInstantiationException extends RuntimeException { private static final String TEXT_TEMPLATE = "Failed to instantiate %s using constructor %s with arguments %s"; private final Class entityType; - private final Constructor constructor; + private final EntityCreatorMetadata entityCreator; private final List constructorArguments; /** @@ -74,8 +77,7 @@ public class MappingInstantiationException extends RuntimeException { super(buildExceptionMessage(entity, arguments, message), cause); this.entityType = entity.map(PersistentEntity::getType).orElse(null); - this.constructor = entity.map(PersistentEntity::getPersistenceConstructor).map(PreferredConstructor::getConstructor) - .orElse(null); + this.entityCreator = entity.map(PersistentEntity::getEntityCreator).orElse(null); this.constructorArguments = arguments; } @@ -84,7 +86,7 @@ public class MappingInstantiationException extends RuntimeException { return entity.map(it -> { - Optional> constructor = Optional.ofNullable(it.getPersistenceConstructor()); + Optional> constructor = Optional.ofNullable(it.getEntityCreator()); List toStringArgs = new ArrayList<>(arguments.size()); for (Object o : arguments) { @@ -98,6 +100,19 @@ public class MappingInstantiationException extends RuntimeException { }).orElse(defaultMessage); } + private static String toString(EntityCreatorMetadata creator) { + + if (creator instanceof PreferredConstructor) { + return toString((PreferredConstructor) creator); + } + + if (creator instanceof FactoryMethod) { + return toString((FactoryMethod) creator); + } + + return creator.toString(); + } + private static String toString(PreferredConstructor preferredConstructor) { Constructor constructor = preferredConstructor.getConstructor(); @@ -114,6 +129,22 @@ public class MappingInstantiationException extends RuntimeException { return constructor.toString(); } + private static String toString(FactoryMethod factoryMethod) { + + Method method = factoryMethod.getFactoryMethod(); + + if (KotlinReflectionUtils.isSupportedKotlinClass(method.getDeclaringClass())) { + + KFunction kotlinFunction = ReflectJvmMapping.getKotlinFunction(method); + + if (kotlinFunction != null) { + return kotlinFunction.toString(); + } + } + + return method.toString(); + } + /** * Returns the type of the entity that was attempted to instantiate. * @@ -127,9 +158,22 @@ public class MappingInstantiationException extends RuntimeException { * The constructor used during the instantiation attempt. * * @return the constructor + * @deprecated since 3.0, use {@link #getEntityCreator()} instead. */ + @Deprecated public Optional> getConstructor() { - return Optional.ofNullable(constructor); + return getEntityCreator().filter(PreferredConstructor.class::isInstance).map(PreferredConstructor.class::cast) + .map(PreferredConstructor::getConstructor); + } + + /** + * The entity creator used during the instantiation attempt. + * + * @return the entity creator + * @since 3.0 + */ + public Optional> getEntityCreator() { + return Optional.ofNullable(entityCreator); } /** diff --git a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java index a852891c5..6ceb00c4b 100644 --- a/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java @@ -15,8 +15,8 @@ */ package org.springframework.data.mapping.model; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.lang.Nullable; /** diff --git a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java index 8e155ae6d..0f2d2ece4 100644 --- a/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java @@ -15,11 +15,11 @@ */ package org.springframework.data.mapping.model; +import org.springframework.data.mapping.EntityCreatorMetadata; import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.lang.Nullable; /** @@ -52,9 +52,9 @@ public class PersistentEntityParameterValueProvider

T getParameterValue(Parameter parameter) { - PreferredConstructor constructor = entity.getPersistenceConstructor(); + EntityCreatorMetadata

creator = entity.getEntityCreator(); - if (constructor != null && constructor.isEnclosingClassParameter(parameter)) { + if (creator != null && creator.isParentParameter(parameter)) { return (T) parent; } diff --git a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java index 09d4fa243..97b95cc8f 100644 --- a/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -26,17 +26,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import javax.inject.Qualifier; - -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.EntityCreatorAnnotation; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.KotlinReflectionUtils; import org.springframework.data.util.TypeInformation; @@ -120,7 +117,7 @@ public interface PreferredConstructorDiscoverer !it.isSynthetic()) // Synthetic constructors should not be considered - .filter(it -> AnnotationUtils.findAnnotation(it, PersistenceConstructor.class) != null) // Explicitly defined constructor trumps + .filter(it -> AnnotationUtils.findAnnotation(it, EntityCreatorAnnotation.class) != null) // Explicitly + // defined + // constructor + // trumps // all .map(it -> buildPreferredConstructor(it, type, entity)) // .findFirst() // diff --git a/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java b/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java index e4335134a..e447f58aa 100644 --- a/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java +++ b/src/main/java/org/springframework/data/mapping/model/ReflectionEntityInstantiator.java @@ -22,10 +22,13 @@ import java.util.Collections; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.FactoryMethod; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.util.ReflectionUtils; /** * {@link EntityInstantiator} that uses the {@link PersistentEntity}'s {@link PreferredConstructor} to instantiate an @@ -44,39 +47,73 @@ enum ReflectionEntityInstantiator implements EntityInstantiator { public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - PreferredConstructor constructor = entity.getPersistenceConstructor(); + EntityCreatorMetadata

creator = entity.getEntityCreator(); - if (constructor == null) { - - try { - Class clazz = entity.getType(); - if (clazz.isArray()) { - Class ctype = clazz; - int dims = 0; - while (ctype.isArray()) { - ctype = ctype.getComponentType(); - dims++; - } - return (T) Array.newInstance(clazz, dims); - } else { - return BeanUtils.instantiateClass(entity.getType()); - } - } catch (BeanInstantiationException e) { - throw new MappingInstantiationException(entity, Collections.emptyList(), e); - } + if (creator == null) { + return instantiateClass(entity); } - int parameterCount = constructor.getConstructor().getParameterCount(); + + int parameterCount = creator.getParameterCount(); Object[] params = parameterCount == 0 ? EMPTY_ARGS : new Object[parameterCount]; int i = 0; - for (Parameter parameter : constructor.getParameters()) { + + for (Parameter parameter : creator.getParameters()) { params[i++] = provider.getParameterValue(parameter); } + if (creator instanceof FactoryMethod) { + + FactoryMethod method = (FactoryMethod) creator; + + try { + + T t = (T) ReflectionUtils.invokeMethod(method.getFactoryMethod(), null, params); + + if (t == null) { + throw new IllegalStateException(String.format("Method %s returned null!", method.getFactoryMethod())); + } + + return t; + + } catch (Exception e) { + throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e); + } + } + try { - return BeanUtils.instantiateClass(constructor.getConstructor(), params); + return BeanUtils.instantiateClass(((PreferredConstructor) creator).getConstructor(), params); } catch (BeanInstantiationException e) { throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e); } } + + @SuppressWarnings("unchecked") + private , P extends PersistentProperty

> T instantiateClass( + E entity) { + + try { + + Class clazz = entity.getType(); + + if (clazz.isArray()) { + + Class ctype = clazz; + int dims = 0; + + while (ctype.isArray()) { + ctype = ctype.getComponentType(); + dims++; + } + + return (T) Array.newInstance(clazz, dims); + + } else { + return BeanUtils.instantiateClass(entity.getType()); + } + + } catch (BeanInstantiationException e) { + throw new MappingInstantiationException(entity, Collections.emptyList(), e); + } + } } diff --git a/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java b/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java index 390d74951..9f3dea938 100644 --- a/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java +++ b/src/main/java/org/springframework/data/mapping/model/SpELExpressionParameterValueProvider.java @@ -16,8 +16,8 @@ package org.springframework.data.mapping.model; import org.springframework.core.convert.ConversionService; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.lang.Nullable; /** diff --git a/src/main/java/org/springframework/data/repository/query/ReturnedType.java b/src/main/java/org/springframework/data/repository/query/ReturnedType.java index af93e97a7..d4a690c62 100644 --- a/src/main/java/org/springframework/data/repository/query/ReturnedType.java +++ b/src/main/java/org/springframework/data/repository/query/ReturnedType.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; import org.springframework.data.projection.ProjectionFactory; @@ -302,7 +303,7 @@ public abstract class ReturnedType { List properties = new ArrayList<>(constructor.getConstructor().getParameterCount()); - for (PreferredConstructor.Parameter parameter : constructor.getParameters()) { + for (Parameter parameter : constructor.getParameters()) { properties.add(parameter.getName()); } diff --git a/src/test/java/org/springframework/data/mapping/ParameterUnitTests.java b/src/test/java/org/springframework/data/mapping/ParameterUnitTests.java index 7ae23d23b..bad7cff5c 100755 --- a/src/test/java/org/springframework/data/mapping/ParameterUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/ParameterUnitTests.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.util.ClassTypeInformation; import org.springframework.data.util.TypeInformation; diff --git a/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java b/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java index 35136de9c..12d8fb3a6 100755 --- a/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.PreferredConstructorDiscovererUnitTests.Outer.Inner; import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; @@ -100,7 +99,7 @@ class PreferredConstructorDiscovererUnitTests

> { assertThat(PreferredConstructorDiscoverer.discover(entity)).satisfies(constructor -> { Parameter parameter = constructor.getParameters().iterator().next(); - assertThat(constructor.isEnclosingClassParameter(parameter)).isTrue(); + assertThat(constructor.isParentParameter(parameter)).isTrue(); }); } diff --git a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java index de5e139a9..dc77e8ee4 100755 --- a/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ClassGeneratingEntityInstantiatorUnitTests.java @@ -32,12 +32,12 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; - +import org.springframework.data.annotation.FactoryMethod; import org.springframework.data.classloadersupport.HidingClassLoader; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.ObjectInstantiator; import org.springframework.data.mapping.model.ClassGeneratingEntityInstantiatorUnitTests.Outer.Inner; import org.springframework.data.util.ClassTypeInformation; @@ -56,7 +56,7 @@ import org.springframework.util.ReflectionUtils; @MockitoSettings(strictness = Strictness.LENIENT) class ClassGeneratingEntityInstantiatorUnitTests

> { - ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator(); + ClassGeneratingEntityInstantiator instance = new ClassGeneratingEntityInstantiator(false); @Mock PersistentEntity entity; @Mock ParameterValueProvider

provider; @@ -83,7 +83,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

PreferredConstructor constructor = PreferredConstructorDiscoverer.discover(Foo.class); doReturn(Foo.class).when(entity).getType(); - doReturn(constructor).when(entity).getPersistenceConstructor(); + doReturn(constructor).when(entity).getEntityCreator(); assertThat(instance.createInstance(entity, provider)).isInstanceOf(Foo.class); @@ -104,8 +104,8 @@ class ClassGeneratingEntityInstantiatorUnitTests

@Test // DATACMNS-134, DATACMNS-578 void createsInnerClassInstanceCorrectly() { - BasicPersistentEntity entity = new BasicPersistentEntity<>(from(Inner.class)); - assertThat(entity.getPersistenceConstructor()).satisfies(constructor -> { + BasicPersistentEntity entity = new BasicPersistentEntity(from(Inner.class)); + assertThat(entity.getEntityCreator()).satisfies(constructor -> { Parameter parameter = constructor.getParameters().iterator().next(); @@ -189,12 +189,43 @@ class ClassGeneratingEntityInstantiatorUnitTests

assertThat(reference.sample.name).isEqualTo("FOO"); } + @Test // DATACMNS-1175 + @SuppressWarnings({ "unchecked", "rawtypes" }) + void createsInstancesWithFactoryMethodCorrectly() { + + PersistentEntity entity = new BasicPersistentEntity<>(from(WithFactoryMethod.class)); + + doReturn(2L, "FOO").when(provider).getParameterValue(any(Parameter.class)); + + ParameterValueProvider

provider = new ParameterValueProvider

() { + + @Override + public T getParameterValue(Parameter parameter) { + + if (parameter.getName().equals("id")) { + return (T) Long.valueOf(1); + } + + if (parameter.getName().equals("name")) { + return (T) "Walter"; + } + + throw new UnsupportedOperationException(parameter.getName()); + } + }; + + WithFactoryMethod result = this.instance.createInstance(entity, provider); + + assertThat(result.id).isEqualTo(1L); + assertThat(result.name).isEqualTo("Hello Walter"); + } + @Test // DATACMNS-578, DATACMNS-1126 void instantiateObjCtorDefault() { doReturn(ObjCtorDefault.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjCtorDefault.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); IntStream.range(0, 2) .forEach(i -> assertThat(this.instance.createInstance(entity, provider)).isInstanceOf(ObjCtorDefault.class)); @@ -205,7 +236,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjCtorNoArgs.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjCtorNoArgs.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); IntStream.range(0, 2).forEach(i -> { @@ -224,7 +255,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjCtor1ParamString.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjCtor1ParamString.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); doReturn("FOO").when(provider).getParameterValue(any()); IntStream.range(0, 2).forEach(i -> { @@ -242,7 +273,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjCtor2ParamStringString.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjCtor2ParamStringString.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); IntStream.range(0, 2).forEach(i -> { @@ -262,7 +293,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjectCtor1ParamInt.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); IntStream.range(0, 2).forEach(i -> { @@ -280,7 +311,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjectCtor1ParamInt.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor1ParamInt.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); assertThatThrownBy(() -> this.instance.createInstance(entity, provider)) // .hasCauseInstanceOf(IllegalArgumentException.class); @@ -292,7 +323,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(ObjectCtor7ParamsString5IntsString.class).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(ObjectCtor7ParamsString5IntsString.class))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); IntStream.range(0, 2).forEach(i -> { @@ -412,7 +443,7 @@ class ClassGeneratingEntityInstantiatorUnitTests

doReturn(type).when(entity).getType(); doReturn(PreferredConstructorDiscoverer.discover(type))// - .when(entity).getPersistenceConstructor(); + .when(entity).getEntityCreator(); } static class Foo { @@ -429,6 +460,23 @@ class ClassGeneratingEntityInstantiatorUnitTests

} } + static class WithFactoryMethod { + + final Long id; + final String name; + + private WithFactoryMethod(Long id, String name) { + + this.id = id; + this.name = name; + } + + @FactoryMethod + public static WithFactoryMethod create(Long id, String name) { + return new WithFactoryMethod(id, "Hello " + name); + } + } + static class Sample { final Long id; diff --git a/src/test/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscovererUnitTests.java b/src/test/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscovererUnitTests.java new file mode 100644 index 000000000..c11539b2c --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/model/EntityCreatorMetadataDiscovererUnitTests.java @@ -0,0 +1,124 @@ +/* + * Copyright 2021 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 + * + * https://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 org.springframework.data.mapping.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.data.annotation.FactoryMethod; +import org.springframework.data.mapping.EntityCreatorMetadata; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.util.ClassTypeInformation; + +/** + * Unit tests for {@link EntityCreatorMetadataDiscoverer}. + * + * @author Mark Paluch + */ +class EntityCreatorMetadataDiscovererUnitTests { + + @Test + void shouldDiscoverAnnotatedFactoryMethod() { + + PersistentEntity entity = new BasicPersistentEntity<>( + ClassTypeInformation.from(FactoryMethodsPerson.class)); + EntityCreatorMetadata creator = EntityCreatorMetadataDiscoverer.discover(entity); + + assertThat(creator).isInstanceOf(org.springframework.data.mapping.FactoryMethod.class); + assertThat(((org.springframework.data.mapping.FactoryMethod) creator).getFactoryMethod().getParameterCount()) + .isEqualTo(2); + } + + @Test + void shouldDiscoverAnnotatedConstructor() { + + PersistentEntity entity = new BasicPersistentEntity<>( + ClassTypeInformation.from(ConstructorPerson.class)); + EntityCreatorMetadata creator = EntityCreatorMetadataDiscoverer.discover(entity); + + assertThat(creator).isInstanceOf(PreferredConstructor.class); + } + + @Test + void shouldDiscoverDefaultConstructor() { + + PersistentEntity entity = new BasicPersistentEntity<>(ClassTypeInformation.from(Person.class)); + EntityCreatorMetadata creator = EntityCreatorMetadataDiscoverer.discover(entity); + + assertThat(creator).isInstanceOf(PreferredConstructor.class); + } + + @Test + void shouldRejectNonStaticFactoryMethod() { + assertThatExceptionOfType(MappingException.class) + .isThrownBy(() -> new BasicPersistentEntity<>(ClassTypeInformation.from(NonStaticFactoryMethod.class))); + } + + static class Person { + + private final String firstname, lastname; + + private Person(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + } + + static class NonStaticFactoryMethod { + + @FactoryMethod + public ConstructorPerson of(String firstname, String lastname) { + return new ConstructorPerson(firstname, lastname); + } + + } + + static class FactoryMethodsPerson { + + private final String firstname, lastname; + + private FactoryMethodsPerson(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + public static FactoryMethodsPerson of(String firstname) { + return new FactoryMethodsPerson(firstname, "unknown"); + } + + @FactoryMethod + public static FactoryMethodsPerson of(String firstname, String lastname) { + return new FactoryMethodsPerson(firstname, lastname); + } + } + + static class ConstructorPerson { + + private final String firstname, lastname; + + private ConstructorPerson(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + public static ConstructorPerson of(String firstname, String lastname) { + return new ConstructorPerson(firstname, lastname); + } + } +} diff --git a/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java index f615ac26f..a7048b97b 100755 --- a/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/EntityInstantiatorsUnitTests.java @@ -44,13 +44,6 @@ class EntityInstantiatorsUnitTests { assertThatIllegalArgumentException().isThrownBy(() -> new EntityInstantiators((EntityInstantiator) null)); } - @Test - void usesReflectionEntityInstantiatorAsDefaultFallback() { - - EntityInstantiators instantiators = new EntityInstantiators(); - assertThat(instantiators.getInstantiatorFor(entity)).isInstanceOf(ClassGeneratingEntityInstantiator.class); - } - @Test void returnsCustomInstantiatorForTypeIfRegistered() { diff --git a/src/test/java/org/springframework/data/mapping/model/FactoryMethodUnitTests.java b/src/test/java/org/springframework/data/mapping/model/FactoryMethodUnitTests.java new file mode 100644 index 000000000..f1b75652f --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/model/FactoryMethodUnitTests.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021 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 + * + * https://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 org.springframework.data.mapping.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.springframework.data.annotation.FactoryMethod; +import org.springframework.data.mapping.Parameter; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.util.ClassTypeInformation; + +/** + * Unit tests for {@link org.springframework.data.mapping.FactoryMethod}. + * + * @author Mark Paluch + */ +class FactoryMethodUnitTests { + + private static EntityInstantiators instantiators = new EntityInstantiators(); + + @Test + void shouldCreateInstanceThroughFactoryMethod() { + + PersistentEntity entity = new BasicPersistentEntity<>( + ClassTypeInformation.from(FactoryPerson.class)); + + FactoryPerson result = instantiators.getInstantiatorFor(entity) + .createInstance(entity, new ParameterValueProvider() { + + @Override + public Object getParameterValue(Parameter parameter) { + + if (parameter.getName().equals("firstname")) { + return "Walter"; + } + + if (parameter.getName().equals("lastname")) { + return "White"; + } + return null; + } + }); + + assertThat(result.firstname).isEqualTo("Walter"); + assertThat(result.lastname).isEqualTo("Mr. White"); + } + + static class FactoryPerson { + + private final String firstname, lastname; + + private FactoryPerson(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + + @FactoryMethod + public static FactoryPerson of(String firstname, String lastname) { + return new FactoryPerson(firstname, "Mr. " + lastname); + } + } +} diff --git a/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java index e092b66fc..604e1629f 100644 --- a/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ParameterizedKotlinInstantiatorUnitTests.java @@ -27,8 +27,8 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.context.SampleMappingContext; import org.springframework.data.mapping.context.SamplePersistentProperty; import org.springframework.test.util.ReflectionTestUtils; diff --git a/src/test/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProviderUnitTests.java b/src/test/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProviderUnitTests.java index 6423f45fa..4a474f26c 100755 --- a/src/test/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProviderUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProviderUnitTests.java @@ -24,11 +24,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; - import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.PersistentEntityParameterValueProviderUnitTests.Outer.Inner; import org.springframework.data.util.ClassTypeInformation; @@ -57,7 +56,7 @@ class PersistentEntityParameterValueProviderUnitTests

{ + assertThat(entity.getEntityCreator()).satisfies(constructor -> { Iterator> iterator = constructor.getParameters().iterator(); ParameterValueProvider

provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider, @@ -76,7 +75,7 @@ class PersistentEntityParameterValueProviderUnitTests

provider = new PersistentEntityParameterValueProvider<>(entity, propertyValueProvider, Optional.of(property)); - assertThat(entity.getPersistenceConstructor()) + assertThat(entity.getEntityCreator()) .satisfies(constructor -> assertThatExceptionOfType(MappingException.class)// .isThrownBy(() -> provider.getParameterValue(constructor.getParameters().iterator().next()))// .withMessageContaining("bar")// diff --git a/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java b/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java index 5b40f5b7f..b5ba63c48 100755 --- a/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/ReflectionEntityInstantiatorUnitTests.java @@ -30,10 +30,10 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; -import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.ReflectionEntityInstantiatorUnitTests.Outer.Inner; import org.springframework.util.ReflectionUtils; @@ -69,7 +69,7 @@ class ReflectionEntityInstantiatorUnitTests

> { PreferredConstructor constructor = PreferredConstructorDiscoverer.discover(Foo.class); - doReturn(constructor).when(entity).getPersistenceConstructor(); + doReturn(constructor).when(entity).getEntityCreator(); Object instance = INSTANCE.createInstance(entity, provider); @@ -91,8 +91,8 @@ class ReflectionEntityInstantiatorUnitTests

> { @Test // DATACMNS-134 void createsInnerClassInstanceCorrectly() { - BasicPersistentEntity entity = new BasicPersistentEntity<>(from(Inner.class)); - assertThat(entity.getPersistenceConstructor()).satisfies(it -> { + BasicPersistentEntity entity = new BasicPersistentEntity(from(Inner.class)); + assertThat(entity.getEntityCreator()).satisfies(it -> { Parameter parameter = it.getParameters().iterator().next(); diff --git a/src/test/java/org/springframework/data/mapping/model/SpelExpressionParameterProviderUnitTests.java b/src/test/java/org/springframework/data/mapping/model/SpelExpressionParameterProviderUnitTests.java index 872621cbe..5e7623f18 100755 --- a/src/test/java/org/springframework/data/mapping/model/SpelExpressionParameterProviderUnitTests.java +++ b/src/test/java/org/springframework/data/mapping/model/SpelExpressionParameterProviderUnitTests.java @@ -27,7 +27,7 @@ import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import org.springframework.core.convert.ConversionService; -import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.Parameter; import org.springframework.data.mapping.model.AbstractPersistentPropertyUnitTests.SamplePersistentProperty; /** diff --git a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt index fc997307e..b06348ce1 100644 --- a/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/convert/KotlinClassGeneratingEntityInstantiatorUnitTests.kt @@ -45,7 +45,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { val constructor = PreferredConstructorDiscoverer.discover(Contact::class.java) every { provider.getParameterValue(any()) }.returnsMany("Walter", "White") - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor every { entity.type } returns constructor.constructor.declaringClass every { entity.typeInformation } returns mockk() @@ -67,7 +67,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { null, null, null, null, null, null, null, null, null, null, /* 20-29 */ null, "Walter", null, "Junior", null) - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor every { entity.type } returns constructor.constructor.declaringClass every { entity.typeInformation } returns mockk() @@ -88,7 +88,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { val constructor = PreferredConstructorDiscoverer.discover(WithBoolean::class.java) every { provider.getParameterValue(any()) } returns null - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor every { entity.type } returns constructor.constructor.declaringClass every { entity.typeInformation } returns mockk() @@ -112,7 +112,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { every { provider.getParameterValue(any()) } returns null every { provider.getParameterValue(any()) } returns null every { provider.getParameterValue(any()) } returns null - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor every { entity.type } returns constructor.constructor.declaringClass every { entity.typeInformation } returns mockk() @@ -135,7 +135,7 @@ class KotlinClassGeneratingEntityInstantiatorUnitTests { val constructor = PreferredConstructorDiscoverer.discover(CustomUser::class.java) every { provider.getParameterValue(any()) } returns "Walter" - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor every { entity.type } returns constructor.constructor.declaringClass every { entity.typeInformation } returns mockk() diff --git a/src/test/kotlin/org/springframework/data/convert/ReflectionEntityInstantiatorDataClassUnitTests.kt b/src/test/kotlin/org/springframework/data/convert/ReflectionEntityInstantiatorDataClassUnitTests.kt index 824c32ae0..8a1ac61dc 100644 --- a/src/test/kotlin/org/springframework/data/convert/ReflectionEntityInstantiatorDataClassUnitTests.kt +++ b/src/test/kotlin/org/springframework/data/convert/ReflectionEntityInstantiatorDataClassUnitTests.kt @@ -42,7 +42,7 @@ class ReflectionEntityInstantiatorDataClassUnitTests { val constructor = PreferredConstructorDiscoverer.discover(Contact::class.java) every { provider.getParameterValue(any()) }.returnsMany("Walter", "White") - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor val instance: Contact = ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider) @@ -57,7 +57,7 @@ class ReflectionEntityInstantiatorDataClassUnitTests { val constructor = PreferredConstructorDiscoverer.discover(ContactWithDefaulting::class.java) every { provider.getParameterValue(any()) }.returnsMany("Walter", null) - every { entity.persistenceConstructor } returns constructor + every { entity.entityCreator } returns constructor val instance: ContactWithDefaulting = ReflectionEntityInstantiator.INSTANCE.createInstance(entity, provider)