Introduce support to create domain objects via factory methods.
Issue #2476.
This commit is contained in:
committed by
Oliver Drotbohm
parent
0ee9c55f05
commit
c4a324e3cf
@@ -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 {
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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<P extends PersistentProperty<P>> {
|
||||
|
||||
/**
|
||||
* 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<?, P> parameter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of parameters.
|
||||
*/
|
||||
default int getParameterCount() {
|
||||
return getParameters().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parameters used by this creator.
|
||||
*/
|
||||
List<Parameter<Object, P>> getParameters();
|
||||
|
||||
/**
|
||||
* @return whether the creator accepts {@link Parameter}s.
|
||||
*/
|
||||
default boolean hasParameters() {
|
||||
return !getParameters().isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -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<T, P extends PersistentProperty<P>> implements EntityCreatorMetadata<P> {
|
||||
|
||||
private final Executable executable;
|
||||
private final List<Parameter<Object, P>> parameters;
|
||||
private final Map<PersistentProperty<?>, 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<Object, P>... 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<Parameter<Object, P>> getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link PersistentProperty} is referenced in a creator argument of the
|
||||
* {@link PersistentEntity} backing this {@link EntityCreatorMetadataSupport}.
|
||||
* <p>
|
||||
* 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!");
|
||||
|
||||
var cached = isPropertyParameterCache.get(property);
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
var result = doGetIsCreatorParameter(property);
|
||||
|
||||
isPropertyParameterCache.put(property, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return executable.toString();
|
||||
}
|
||||
|
||||
private boolean doGetIsCreatorParameter(PersistentProperty<?> property) {
|
||||
|
||||
for (Parameter<?, P> parameter : parameters) {
|
||||
if (parameter.maps(property)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
|
||||
|
||||
/**
|
||||
* 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<Object, P>... parameters) {
|
||||
|
||||
super(factoryMethod, parameters);
|
||||
ReflectionUtils.makeAccessible(factoryMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link Constructor}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Method getFactoryMethod() {
|
||||
return (Method) getExecutable();
|
||||
}
|
||||
|
||||
}
|
||||
208
src/main/java/org/springframework/data/mapping/Parameter.java
Normal file
208
src/main/java/org/springframework/data/mapping/Parameter.java
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* 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 <T> the type of the parameter
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class Parameter<T, P extends PersistentProperty<P>> {
|
||||
|
||||
private final @Nullable String name;
|
||||
private final TypeInformation<T> type;
|
||||
private final MergedAnnotations annotations;
|
||||
private final String key;
|
||||
private final @Nullable PersistentEntity<T, P> entity;
|
||||
|
||||
private final Lazy<Boolean> enclosingClassCache;
|
||||
private final Lazy<Boolean> 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<T> type, Annotation[] annotations,
|
||||
@Nullable PersistentEntity<T, P> 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();
|
||||
}
|
||||
|
||||
var 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<T> 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<T> 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<?, ?> parameter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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() {
|
||||
var 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) {
|
||||
|
||||
var entity = this.entity;
|
||||
var name = this.name;
|
||||
|
||||
var referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name);
|
||||
|
||||
return property.equals(referencedProperty);
|
||||
}
|
||||
|
||||
boolean isEnclosingClassParameter() {
|
||||
return enclosingClassCache.get();
|
||||
}
|
||||
}
|
||||
@@ -49,10 +49,24 @@ public interface PersistentEntity<T, P extends PersistentProperty<P>> 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<T, P> 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<P> 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<T, P extends PersistentProperty<P>> 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.
|
||||
|
||||
@@ -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<T, P extends PersistentProperty<P>> {
|
||||
public final class PreferredConstructor<T, P extends PersistentProperty<P>> extends EntityCreatorMetadataSupport<T, P> {
|
||||
|
||||
private final Constructor<T> constructor;
|
||||
private final List<Parameter<Object, P>> parameters;
|
||||
private final Map<PersistentProperty<?>, 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<T, P extends PersistentProperty<P>> {
|
||||
@SafeVarargs
|
||||
public PreferredConstructor(Constructor<T> constructor, Parameter<Object, P>... 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<T, P extends PersistentProperty<P>> {
|
||||
* @return
|
||||
*/
|
||||
public Constructor<T> getConstructor() {
|
||||
return constructor;
|
||||
return (Constructor<T>) getExecutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Parameter}s of the constructor.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Parameter<Object, P>> 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<T, P extends PersistentProperty<P>> {
|
||||
* @return
|
||||
*/
|
||||
public boolean isNoArgConstructor() {
|
||||
return parameters.isEmpty();
|
||||
return !hasParameters();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,41 +80,22 @@ public class PreferredConstructor<T, P extends PersistentProperty<P>> {
|
||||
* @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}.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
Assert.notNull(property, "Property must not be null!");
|
||||
|
||||
var cached = isPropertyParameterCache.get(property);
|
||||
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
var result = false;
|
||||
for (Parameter<?, P> parameter : parameters) {
|
||||
if (parameter.maps(property)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isPropertyParameterCache.put(property, result);
|
||||
|
||||
return result;
|
||||
@Override
|
||||
public boolean isParentParameter(Parameter<?, P> parameter) {
|
||||
return isEnclosingClassParameter(parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,176 +117,4 @@ public class PreferredConstructor<T, P extends PersistentProperty<P>> {
|
||||
return parameters.get(0).equals(parameter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to represent constructor parameters.
|
||||
*
|
||||
* @param <T> the type of the parameter
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public static class Parameter<T, P extends PersistentProperty<P>> {
|
||||
|
||||
private final @Nullable String name;
|
||||
private final TypeInformation<T> type;
|
||||
private final MergedAnnotations annotations;
|
||||
private final String key;
|
||||
private final @Nullable PersistentEntity<T, P> entity;
|
||||
|
||||
private final Lazy<Boolean> enclosingClassCache;
|
||||
private final Lazy<Boolean> 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<T> type, Annotation[] annotations,
|
||||
@Nullable PersistentEntity<T, P> 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();
|
||||
}
|
||||
|
||||
var 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<T> 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<T> 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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(o instanceof Parameter<?, ?> parameter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
var 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) {
|
||||
|
||||
var entity = this.entity;
|
||||
var name = this.name;
|
||||
|
||||
var referencedProperty = entity == null ? null : name == null ? null : entity.getPersistentProperty(name);
|
||||
|
||||
return property.equals(referencedProperty);
|
||||
}
|
||||
|
||||
private boolean isEnclosingClassParameter() {
|
||||
return enclosingClassCache.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> 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<T, P> constructor;
|
||||
private final @Nullable EntityCreatorMetadata<P> creator;
|
||||
private final TypeInformation<T> information;
|
||||
private final List<P> properties;
|
||||
private final List<P> persistentPropertiesCache;
|
||||
@@ -109,7 +109,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> 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,16 +124,23 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> 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())));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PreferredConstructor<T, P> getPersistenceConstructor() {
|
||||
return constructor;
|
||||
return creator instanceof PreferredConstructor ? (PreferredConstructor<T, P>) creator : null;
|
||||
}
|
||||
|
||||
public boolean isConstructorArgument(PersistentProperty<?> property) {
|
||||
return constructor != null && constructor.isConstructorParameter(property);
|
||||
@Override
|
||||
public EntityCreatorMetadata<P> getEntityCreator() {
|
||||
return creator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCreatorArgument(PersistentProperty<?> property) {
|
||||
return creator != null && creator.isCreatorParameter(property);
|
||||
}
|
||||
|
||||
public boolean isIdProperty(PersistentProperty<?> property) {
|
||||
|
||||
@@ -33,19 +33,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
|
||||
@@ -64,11 +67,21 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
private volatile Map<TypeInformation<?>, 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,18 +135,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +158,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()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -169,11 +187,26 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
return true;
|
||||
}
|
||||
|
||||
var persistenceConstructor = entity.getPersistenceConstructor();
|
||||
if (persistenceConstructor == null || Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) {
|
||||
var entityCreator = entity.getEntityCreator();
|
||||
|
||||
if (entityCreator == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entityCreator instanceof PreferredConstructor<?, ?> persistenceConstructor) {
|
||||
|
||||
if (Modifier.isPrivate(persistenceConstructor.getConstructor().getModifiers())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (entityCreator instanceof FactoryMethod<?, ?> factoryMethod) {
|
||||
|
||||
if (Modifier.isPrivate(factoryMethod.getFactoryMethod().getModifiers())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ClassUtils.isPresent(ObjectInstantiator.class.getName(), type.getClassLoader())) {
|
||||
return true;
|
||||
}
|
||||
@@ -194,7 +227,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
|
||||
@@ -202,7 +235,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();
|
||||
@@ -236,7 +269,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
var params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
|
||||
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
|
||||
|
||||
try {
|
||||
return (T) instantiator.newInstance(params);
|
||||
@@ -254,13 +287,13 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
* @return
|
||||
*/
|
||||
static <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> constructor, ParameterValueProvider<P> provider) {
|
||||
@Nullable EntityCreatorMetadata<P> constructor, ParameterValueProvider<P> provider) {
|
||||
|
||||
if (constructor == null || !constructor.hasParameters()) {
|
||||
return allocateArguments(0);
|
||||
}
|
||||
|
||||
var params = allocateArguments(constructor.getConstructor().getParameterCount());
|
||||
var params = allocateArguments(constructor.getParameterCount());
|
||||
|
||||
var index = 0;
|
||||
for (Parameter<?, P> parameter : constructor.getParameters()) {
|
||||
@@ -303,7 +336,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
var params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
|
||||
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
|
||||
|
||||
throw new MappingInstantiationException(entity, Arrays.asList(params),
|
||||
new BeanInstantiationException(typeToCreate, "Class is abstract"));
|
||||
@@ -353,7 +386,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
private static final String INIT = "<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[] {
|
||||
@@ -367,7 +400,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
* @return
|
||||
*/
|
||||
public Class<?> generateCustomInstantiatorClass(PersistentEntity<?, ?> entity,
|
||||
@Nullable PreferredConstructor<?, ?> constructor) {
|
||||
@Nullable EntityCreatorMetadata<?> constructor) {
|
||||
|
||||
var className = generateClassName(entity);
|
||||
var type = entity.getType();
|
||||
@@ -404,11 +437,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) {
|
||||
|
||||
var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
|
||||
@@ -417,7 +450,7 @@ class ClassGeneratingEntityInstantiator implements EntityInstantiator {
|
||||
|
||||
visitDefaultConstructor(cw);
|
||||
|
||||
visitCreateMethod(cw, entity, constructor);
|
||||
visitCreateMethod(cw, entity, entityCreator);
|
||||
|
||||
cw.visitEnd();
|
||||
|
||||
@@ -440,49 +473,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) {
|
||||
|
||||
var entityTypeResourcePath = Type.getInternalName(entity.getType());
|
||||
|
||||
var 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<?, ?> constructor) {
|
||||
visitConstructorCreation(constructor, mv, entityTypeResourcePath);
|
||||
}
|
||||
|
||||
var ctor = constructor.getConstructor();
|
||||
var parameterTypes = ctor.getParameterTypes();
|
||||
List<? extends Parameter<Object, ?>> parameters = constructor.getParameters();
|
||||
if (entityCreator instanceof FactoryMethod<?, ?> factoryMethod) {
|
||||
visitFactoryMethodCreation(factoryMethod, mv, entityTypeResourcePath);
|
||||
}
|
||||
|
||||
for (var 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);
|
||||
var ctor = constructor.getConstructor();
|
||||
var parameterTypes = ctor.getParameterTypes();
|
||||
List<? extends Parameter<Object, ?>> 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);
|
||||
var 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]));
|
||||
}
|
||||
var method = factoryMethod.getFactoryMethod();
|
||||
var parameterTypes = method.getParameterTypes();
|
||||
List<? extends Parameter<Object, ?>> 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<? extends Parameter<Object, ?>> parameters) {
|
||||
|
||||
for (var i = 0; i < parameterTypes.length; i++) {
|
||||
|
||||
mv.visitVarInsn(ALOAD, 1);
|
||||
|
||||
visitArrayIndex(mv, i);
|
||||
|
||||
mv.visitInsn(AALOAD);
|
||||
|
||||
if (parameterTypes[i].isPrimitive()) {
|
||||
|
||||
mv.visitInsn(DUP);
|
||||
var 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,8 +575,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -529,52 +592,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 + "'");
|
||||
|
||||
@@ -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.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.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.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 <T>
|
||||
* @param <P>
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
public static <T, P extends PersistentProperty<P>> EntityCreatorMetadata<P> discover(PersistentEntity<T, P> entity) {
|
||||
|
||||
var declaredConstructors = entity.getType().getDeclaredConstructors();
|
||||
var declaredMethods = entity.getType().getDeclaredMethods();
|
||||
|
||||
var hasAnnotatedFactoryMethod = findAnnotation(EntityCreatorAnnotation.class, declaredMethods);
|
||||
var hasAnnotatedConstructor = findAnnotation(EntityCreatorAnnotation.class, declaredConstructors);
|
||||
|
||||
if (hasAnnotatedConstructor && hasAnnotatedFactoryMethod) {
|
||||
throw new MappingException(
|
||||
"Invalid usage of @Factory and @PersistenceConstructor on %s. Only one annotation type permitted to indicate how entity instances should be created."
|
||||
.formatted(entity.getType().getName()));
|
||||
}
|
||||
|
||||
if (hasAnnotatedFactoryMethod) {
|
||||
|
||||
var candidates = discoverFactoryMethods(entity, declaredMethods);
|
||||
|
||||
if (candidates.size() == 1) {
|
||||
return getFactoryMethod(entity, candidates.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
return PreferredConstructorDiscoverer.discover(entity);
|
||||
}
|
||||
|
||||
private static <T, P extends PersistentProperty<P>> List<Method> discoverFactoryMethods(PersistentEntity<T, P> entity,
|
||||
Method[] declaredMethods) {
|
||||
|
||||
List<Method> candidates = new ArrayList<>();
|
||||
|
||||
for (var 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 <T, P extends PersistentProperty<P>> FactoryMethod<Object, P> getFactoryMethod(
|
||||
PersistentEntity<T, P> entity, Method method) {
|
||||
|
||||
Parameter<Object, P>[] parameters = new Parameter[method.getParameterCount()];
|
||||
var parameterAnnotations = method.getParameterAnnotations();
|
||||
var types = entity.getTypeInformation().getParameterTypes(method);
|
||||
|
||||
var parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
|
||||
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
|
||||
var name = parameterNames == null || parameterNames.length <= i ? null : parameterNames[i];
|
||||
var type = types.get(i);
|
||||
var 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(
|
||||
"@Factory can only be used on static methods. Offending method: %s".formatted(method));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> boolean isFactoryMethod(Method method, Class<T> 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<? extends Annotation> annotationType, AnnotatedElement... elements) {
|
||||
|
||||
for (var element : elements) {
|
||||
if (MergedAnnotations.from(element).isPresent(annotationType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,9 @@ import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.data.annotation.PersistenceConstructor;
|
||||
import org.springframework.data.mapping.Parameter;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor.Parameter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -81,22 +81,22 @@ public class InstantiationAwarePropertyAccessor<T> implements PersistentProperty
|
||||
return;
|
||||
}
|
||||
|
||||
var constructor = owner.getPersistenceConstructor();
|
||||
var 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()));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -45,9 +46,10 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
|
||||
@Override
|
||||
protected EntityInstantiator doCreateEntityInstantiator(PersistentEntity<?, ?> entity) {
|
||||
|
||||
var constructor = entity.getPersistenceConstructor();
|
||||
var creator = entity.getEntityCreator();
|
||||
|
||||
if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType()) && constructor != null) {
|
||||
if (KotlinReflectionUtils.isSupportedKotlinClass(entity.getType())
|
||||
&& creator instanceof PreferredConstructor<?, ?> constructor) {
|
||||
|
||||
var defaultConstructor = new DefaultingKotlinConstructorResolver(entity)
|
||||
.getDefaultConstructor();
|
||||
@@ -78,9 +80,9 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
|
||||
DefaultingKotlinConstructorResolver(PersistentEntity<?, ?> entity) {
|
||||
|
||||
var hit = resolveDefaultConstructor(entity);
|
||||
var persistenceConstructor = entity.getPersistenceConstructor();
|
||||
var creator = entity.getEntityCreator();
|
||||
|
||||
if (hit != null && persistenceConstructor != null) {
|
||||
if (hit != null && creator instanceof PreferredConstructor<?, ?> persistenceConstructor) {
|
||||
this.defaultConstructor = new PreferredConstructor<>(hit,
|
||||
persistenceConstructor.getParameters().toArray(new Parameter[0]));
|
||||
} else {
|
||||
@@ -91,9 +93,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
|
||||
@Nullable
|
||||
private static Constructor<?> resolveDefaultConstructor(PersistentEntity<?, ?> entity) {
|
||||
|
||||
var persistenceConstructor = entity.getPersistenceConstructor();
|
||||
|
||||
if (persistenceConstructor == null) {
|
||||
if (!(entity.getEntityCreator()instanceof PreferredConstructor<?, ?> persistenceConstructor)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
var params = extractInvocationArguments(entity.getPersistenceConstructor(), provider);
|
||||
var params = extractInvocationArguments(entity.getEntityCreator(), provider);
|
||||
|
||||
try {
|
||||
return (T) instantiator.newInstance(params);
|
||||
@@ -201,17 +201,17 @@ class KotlinClassGeneratingEntityInstantiator extends ClassGeneratingEntityInsta
|
||||
}
|
||||
|
||||
private <P extends PersistentProperty<P>, T> Object[] extractInvocationArguments(
|
||||
@Nullable PreferredConstructor<? extends T, P> preferredConstructor, ParameterValueProvider<P> provider) {
|
||||
@Nullable EntityCreatorMetadata<P> entityCreator, ParameterValueProvider<P> provider) {
|
||||
|
||||
if (preferredConstructor == null) {
|
||||
throw new IllegalArgumentException("PreferredConstructor must not be null!");
|
||||
if (entityCreator == null) {
|
||||
throw new IllegalArgumentException("EntityCreator must not be null!");
|
||||
}
|
||||
|
||||
var params = allocateArguments(synthetic.getParameterCount()
|
||||
+ KotlinDefaultMask.getMaskCount(synthetic.getParameterCount()) + /* DefaultConstructorMarker */1);
|
||||
var userParameterCount = kParameters.size();
|
||||
|
||||
var parameters = preferredConstructor.getParameters();
|
||||
var parameters = entityCreator.getParameters();
|
||||
|
||||
// Prepare user-space arguments
|
||||
for (var i = 0; i < userParameterCount; i++) {
|
||||
|
||||
@@ -22,6 +22,8 @@ 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;
|
||||
@@ -42,7 +44,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<Object> constructorArguments;
|
||||
|
||||
/**
|
||||
@@ -73,8 +75,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;
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ public class MappingInstantiationException extends RuntimeException {
|
||||
|
||||
return entity.map(it -> {
|
||||
|
||||
Optional<? extends PreferredConstructor<?, ?>> constructor = Optional.ofNullable(it.getPersistenceConstructor());
|
||||
Optional<? extends EntityCreatorMetadata<?>> constructor = Optional.ofNullable(it.getEntityCreator());
|
||||
List<String> toStringArgs = new ArrayList<>(arguments.size());
|
||||
|
||||
for (var o : arguments) {
|
||||
@@ -97,6 +98,19 @@ public class MappingInstantiationException extends RuntimeException {
|
||||
}).orElse(defaultMessage);
|
||||
}
|
||||
|
||||
private static String toString(EntityCreatorMetadata<?> creator) {
|
||||
|
||||
if (creator instanceof PreferredConstructor<?, ?> c) {
|
||||
return toString(c);
|
||||
}
|
||||
|
||||
if (creator instanceof FactoryMethod<?, ?> m) {
|
||||
return toString(m);
|
||||
}
|
||||
|
||||
return creator.toString();
|
||||
}
|
||||
|
||||
private static String toString(PreferredConstructor<?, ?> preferredConstructor) {
|
||||
|
||||
var constructor = preferredConstructor.getConstructor();
|
||||
@@ -113,6 +127,22 @@ public class MappingInstantiationException extends RuntimeException {
|
||||
return constructor.toString();
|
||||
}
|
||||
|
||||
private static String toString(FactoryMethod<?, ?> factoryMethod) {
|
||||
|
||||
var constructor = factoryMethod.getFactoryMethod();
|
||||
|
||||
if (KotlinReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
|
||||
|
||||
var kotlinFunction = ReflectJvmMapping.getKotlinFunction(constructor);
|
||||
|
||||
if (kotlinFunction != null) {
|
||||
return kotlinFunction.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return constructor.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the entity that was attempted to instantiate.
|
||||
*
|
||||
@@ -126,9 +156,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<Constructor<?>> 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<EntityCreatorMetadata<?>> getEntityCreator() {
|
||||
return Optional.ofNullable(entityCreator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
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.lang.Nullable;
|
||||
|
||||
/**
|
||||
@@ -47,10 +47,10 @@ public class PersistentEntityParameterValueProvider<P extends PersistentProperty
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getParameterValue(Parameter<T, P> parameter) {
|
||||
|
||||
var constructor = entity.getPersistenceConstructor();
|
||||
var creator = entity.getEntityCreator();
|
||||
|
||||
if (constructor != null && constructor.isEnclosingClassParameter(parameter)) {
|
||||
return (T) parent;
|
||||
if (creator.isParentParameter(parameter)) {
|
||||
return (T) parent;
|
||||
}
|
||||
|
||||
var name = parameter.getName();
|
||||
|
||||
@@ -24,17 +24,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;
|
||||
@@ -114,7 +111,7 @@ public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<
|
||||
continue;
|
||||
}
|
||||
|
||||
if (AnnotationUtils.findAnnotation(candidate, PersistenceConstructor.class) != null) {
|
||||
if (AnnotationUtils.findAnnotation(candidate, EntityCreatorAnnotation.class) != null) {
|
||||
return buildPreferredConstructor(candidate, type, entity);
|
||||
}
|
||||
|
||||
@@ -148,7 +145,10 @@ public interface PreferredConstructorDiscoverer<T, P extends PersistentProperty<
|
||||
|
||||
return Arrays.stream(rawOwningType.getDeclaredConstructors()) //
|
||||
.filter(it -> !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() //
|
||||
|
||||
@@ -22,10 +22,12 @@ import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.BeanInstantiationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
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 +46,58 @@ enum ReflectionEntityInstantiator implements EntityInstantiator {
|
||||
public <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T createInstance(E entity,
|
||||
ParameterValueProvider<P> provider) {
|
||||
|
||||
var constructor = entity.getPersistenceConstructor();
|
||||
var creator = entity.getEntityCreator();
|
||||
|
||||
if (constructor == null) {
|
||||
|
||||
try {
|
||||
Class<?> clazz = entity.getType();
|
||||
if (clazz.isArray()) {
|
||||
var ctype = clazz;
|
||||
var 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);
|
||||
}
|
||||
var parameterCount = constructor.getConstructor().getParameterCount();
|
||||
var parameterCount = creator.getParameterCount();
|
||||
|
||||
var params = parameterCount == 0 ? EMPTY_ARGS : new Object[parameterCount];
|
||||
var i = 0;
|
||||
for (Parameter<?, P> parameter : constructor.getParameters()) {
|
||||
for (Parameter<?, P> parameter : creator.getParameters()) {
|
||||
params[i++] = provider.getParameterValue(parameter);
|
||||
}
|
||||
|
||||
if (creator instanceof FactoryMethod method) {
|
||||
|
||||
try {
|
||||
var t = (T) ReflectionUtils.invokeMethod(method.getFactoryMethod(), null, params);
|
||||
|
||||
if (t == null) {
|
||||
throw new IllegalStateException("Method %s returned null!".formatted(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<T, ?>) creator).getConstructor(), params);
|
||||
} catch (BeanInstantiationException e) {
|
||||
throw new MappingInstantiationException(entity, new ArrayList<>(Arrays.asList(params)), e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T, E extends PersistentEntity<? extends T, P>, P extends PersistentProperty<P>> T instantiateClass(
|
||||
E entity) {
|
||||
|
||||
try {
|
||||
Class<?> clazz = entity.getType();
|
||||
if (clazz.isArray()) {
|
||||
var ctype = clazz;
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,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;
|
||||
@@ -261,7 +262,7 @@ public abstract class ReturnedType {
|
||||
|
||||
List<String> properties = new ArrayList<>(constructor.getConstructor().getParameterCount());
|
||||
|
||||
for (PreferredConstructor.Parameter<Object, ?> parameter : constructor.getParameters()) {
|
||||
for (Parameter<Object, ?> parameter : constructor.getParameters()) {
|
||||
properties.add(parameter.getName());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user