From 35697d639ab39f50c42d80038ae99d98d43d9827 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 23 Jan 2012 11:25:18 +0100 Subject: [PATCH] DATACMNS-122 - Introduced further abstractions to improve entity instantiation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PersistentEntity now has a isConstructorArgument(…) allowing to find out whether a PersistentProperty is referred to from a constructor argument. The PreferredConstructor abstraction now keeps track of the PersistentEntity it is built for and thus allows finding out whether a constructor Parameter maps a PersistentProperty. Removed bean creation responsibility from BeanWrapper and introduced EntityInstantiator abstraction to allow the entity instantiation mechanism be short circuited via custom implementations. The reflection based implementation from BeanWrapper is now residing in ReflectionEntityInstantiator. --- .../data/convert/EntityInstantiator.java | 37 ++++++ .../data/convert/EntityInstantiators.java | 95 +++++++++++++++ .../convert/ReflectionEntityInstantiator.java | 80 +++++++++++++ .../convert/SimpleTypeInformationMapper.java | 2 +- .../data/mapping/PersistentEntity.java | 31 ++++- .../data/mapping/PreferredConstructor.java | 65 ++++++++-- .../data/mapping/PropertyPath.java | 23 ++-- .../mapping/model/BasicPersistentEntity.java | 24 ++-- .../data/mapping/model/BeanWrapper.java | 71 +---------- ...va => DefaultSpELExpressionEvaluator.java} | 31 ++--- .../mapping/model/ParameterValueProvider.java | 15 ++- ...ersistentEntityParameterValueProvider.java | 79 ++++++++++++ .../model/PreferredConstructorDiscoverer.java | 52 +++++--- .../mapping/model/PropertyValueProvider.java | 34 ++++++ .../data/mapping/model/SpELContext.java | 112 ++++++++++++++++++ .../model/SpELExpressionEvaluator.java | 32 +++++ .../support/ReflectionEntityInformation.java | 94 +++++++++++++++ .../convert/EntityInstantiatorsUnitTests.java | 87 ++++++++++++++ .../ReflectionEntityInstantiatorUnitTest.java | 87 ++++++++++++++ .../SimpleTypeInformationMapperUnitTests.java | 73 ++++++++++++ ...eferredConstructorDiscovererUnitTests.java | 40 +++---- 21 files changed, 1003 insertions(+), 161 deletions(-) create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiator.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiators.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java rename spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/{SpELAwareParameterValueProvider.java => DefaultSpELExpressionEvaluator.java} (54%) create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PropertyValueProvider.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELContext.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java create mode 100644 spring-data-commons-core/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java create mode 100644 spring-data-commons-core/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiator.java b/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiator.java new file mode 100644 index 000000000..711376876 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.model.ParameterValueProvider; + +/** + * SPI to abstract strategies to create instances for {@link PersistentEntities}. + * + * @author Oliver Gierke + */ +public interface EntityInstantiator { + + /** + * Creates a new instance of the given entity using the given source to pull data from. + * + * @param entity will not be {@literal null}. + * @param provider will not be {@literal null}. + * @return + */ + , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiators.java b/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiators.java new file mode 100644 index 000000000..93595078a --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/convert/EntityInstantiators.java @@ -0,0 +1,95 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import java.util.Collections; +import java.util.Map; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.util.Assert; + +/** + * Simple value object allowing access to {@link EntityInstantiator} instances for a given type falling back to a + * default one. + * + * @author Oliver Gierke + */ +public class EntityInstantiators { + + private final EntityInstantiator fallback; + private final Map, EntityInstantiator> customInstantiators; + + /** + * Creates a new {@link EntityInstantiators} using the default fallback instantiator and no custom ones. + */ + public EntityInstantiators() { + this(Collections., EntityInstantiator> emptyMap()); + } + + /** + * Creates a new {@link EntityInstantiators} using the given {@link EntityInstantiator} as fallback. + * + * @param fallback must not be {@literal null}. + */ + public EntityInstantiators(EntityInstantiator fallback) { + this(fallback, Collections., EntityInstantiator> emptyMap()); + } + + /** + * Creates a new {@link EntityInstantiators} using the default fallback instantiator and the given custom ones. + * + * @param customInstantiators must not be {@literal null}. + */ + public EntityInstantiators(Map, EntityInstantiator> customInstantiators) { + this(ReflectionEntityInstantiator.INSTANCE, customInstantiators); + } + + /** + * Creates a new {@link EntityInstantiator} using the given fallback {@link EntityInstantiator} and the given custom + * ones. + * + * @param fallback must not be {@literal null}. + * @param customInstantiators must not be {@literal null}. + */ + public EntityInstantiators(EntityInstantiator defaultInstantiator, + Map, EntityInstantiator> customInstantiators) { + + Assert.notNull(defaultInstantiator); + Assert.notNull(customInstantiators); + + this.fallback = defaultInstantiator; + this.customInstantiators = customInstantiators; + } + + /** + * Returns the {@link EntityInstantiator} to be used to create the given {@link PersistentEntity}. + * + * @param entity must not be {@literal null}. + * @return will never be {@literal null}. + */ + public EntityInstantiator getInstantiatorFor(PersistentEntity entity) { + + Assert.notNull(entity); + Class type = entity.getType(); + + if (!customInstantiators.containsKey(type)) { + return fallback; + } + + EntityInstantiator instantiator = customInstantiators.get(entity.getType()); + return instantiator == null ? fallback : instantiator; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java b/spring-data-commons-core/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java new file mode 100644 index 000000000..21c44227b --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/convert/ReflectionEntityInstantiator.java @@ -0,0 +1,80 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.BeanInstantiationException; +import org.springframework.beans.BeanUtils; +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.MappingInstantiationException; +import org.springframework.data.mapping.model.ParameterValueProvider; + +/** + * {@link EntityInstantiator} that uses the {@link PersistentEntity}'s {@link MappedConstructor} to instantiate an + * instance of the entity via reflection. + * + * @author Oliver Gierke + */ +public enum ReflectionEntityInstantiator implements EntityInstantiator { + + INSTANCE; + + @SuppressWarnings("unchecked") + public , P extends PersistentProperty

> T createInstance(E entity, + ParameterValueProvider

provider) { + + PreferredConstructor constructor = entity.getPersistenceConstructor(); + + 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(e.getMessage(), e); + } + } + + List params = new ArrayList(); + if (null != provider && constructor.hasParameters()) { + for (Parameter parameter : constructor.getParameters()) { + params.add(provider.getParameterValue(parameter)); + } + } + + try { + return (T) BeanUtils.instantiateClass(constructor.getConstructor(), params.toArray()); + } catch (BeanInstantiationException e) { + throw new MappingInstantiationException(e.getMessage(), e); + } + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java b/spring-data-commons-core/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java index 05c2f9b70..261f052cd 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/convert/SimpleTypeInformationMapper.java @@ -68,6 +68,6 @@ public class SimpleTypeInformationMapper implements TypeInformationMapper { */ public String createAliasFor(TypeInformation type) { - return type == null ? null : type.getType().getName(); + return type.getType().getName(); } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java index 6dc37e33d..e77cdc4e0 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PersistentEntity.java @@ -1,3 +1,18 @@ +/* + * Copyright (c) 2011-2012 by the original author(s). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.springframework.data.mapping; import org.springframework.data.util.TypeInformation; @@ -5,9 +20,9 @@ import org.springframework.data.util.TypeInformation; /** * Represents a persistent entity * + * @author Oliver Gierke * @author Graeme Rocher * @author Jon Brisbin - * @author Oliver Gierke */ public interface PersistentEntity> { @@ -23,7 +38,17 @@ public interface PersistentEntity> { * * @return {@literal null} in case no suitable constructor for automatic construction can be found. */ - PreferredConstructor getPreferredConstructor(); + PreferredConstructor getPersistenceConstructor(); + + /** + * Returns whether the given {@link PersistentProperty} is referred to by a constructor argument of the + * {@link PersistentEntity}. + * + * @param property + * @return true if the given {@link PersistentProperty} is referred to by a constructor argument or {@literal false} if + * not or {@literal null}. + */ + boolean isConstructorArgument(P property); /** * Returns the id property of the {@link PersistentEntity}. Must never return {@literal null} as a @@ -47,7 +72,7 @@ public interface PersistentEntity> { * @return The underlying Java class for this entity */ Class getType(); - + /** * Returns the alias to be used when storing type information. Might be {@literal null} to indicate that there was no * alias defined through the mapping metadata. diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index 09a5bbe64..f97802555 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,17 +25,18 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; 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. * - * @author Jon Brisbin * @author Oliver Gierke + * @author Jon Brisbin */ -public class PreferredConstructor { +public class PreferredConstructor> { private final Constructor constructor; - private final List> parameters; + private final List> parameters; /** * Creates a new {@link PreferredConstructor} from the given {@link Constructor} and {@link Parameter}s. @@ -43,7 +44,7 @@ public class PreferredConstructor { * @param constructor * @param parameters */ - public PreferredConstructor(Constructor constructor, Parameter... parameters) { + public PreferredConstructor(Constructor constructor, Parameter... parameters) { Assert.notNull(constructor); Assert.notNull(parameters); @@ -67,7 +68,7 @@ public class PreferredConstructor { * * @return */ - public Iterable> getParameters() { + public Iterable> getParameters() { return parameters; } @@ -99,18 +100,39 @@ public class PreferredConstructor { public boolean isExplicitlyAnnotated() { return constructor.isAnnotationPresent(PersistenceConstructor.class); } + + /** + * Returns whether the given {@link PersistentProperty} is referenced in a constructor argument of the + * {@link PersistentEntity} backing this {@link MappedConstructor}. + * + * @param property must not be {@literal null}. + * @return + */ + public boolean isConstructorParameter(P property) { + + Assert.notNull(property); + + for (Parameter parameter : parameters) { + if (parameter.maps(property)) { + return true; + } + } + + return false; + } /** * Value object to represent constructor parameters. * - * @param the type of the paramter + * @param the type of the parameter * @author Oliver Gierke */ - public static class Parameter { + public static class Parameter> { private final String name; private final TypeInformation type; private final String key; + private final PersistentEntity entity; /** * Creates a new {@link Parameter} with the given name, {@link TypeInformation} as well as an array of @@ -120,8 +142,9 @@ public class PreferredConstructor { * @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 can be {@literal null}. */ - public Parameter(String name, TypeInformation type, Annotation[] annotations) { + public Parameter(String name, TypeInformation type, Annotation[] annotations, PersistentEntity entity) { Assert.notNull(type); Assert.notNull(annotations); @@ -129,6 +152,7 @@ public class PreferredConstructor { this.name = name; this.type = type; this.key = getValue(annotations); + this.entity = entity; } private String getValue(Annotation[] annotations) { @@ -172,8 +196,29 @@ public class PreferredConstructor { * * @return */ - public String getKey() { + public String getSpelExpression() { return key; } + + /** + * Returns whether the constructor parameter is equipped with a SpEL expression. + * + * @return + */ + public boolean hasSpelExpression() { + return StringUtils.hasText(getSpelExpression()); + } + + /** + * Returns whether the {@link Parameter} maps the given {@link PersistentProperty}. + * + * @param property + * @return + */ + boolean maps(P property) { + + P referencedProperty = entity == null ? null : entity.getPersistentProperty(name); + return property == null ? false : property.equals(referencedProperty); + } } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java index 6cca43841..974ecc439 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/PropertyPath.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-2012 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. @@ -47,8 +47,8 @@ public class PropertyPath implements Iterable { /** * Creates a leaf {@link PropertyPath} (no nested ones) with the given name inside the given owning type. * - * @param name - * @param owningType + * @param name must not be {@literal null} or empty. + * @param owningType must not be {@literal null}. */ PropertyPath(String name, Class owningType) { @@ -58,8 +58,8 @@ public class PropertyPath implements Iterable { /** * Creates a leaf {@link PropertyPath} (no nested ones with the given name and owning type. * - * @param name - * @param owningType + * @param name must not be {@literal null} or empty. + * @param owningType must not be {@literal null}. */ PropertyPath(String name, TypeInformation owningType) { @@ -83,8 +83,8 @@ public class PropertyPath implements Iterable { * Creates a {@link PropertyPath} with the given name inside the given owning type and tries to resolve the other * {@link String} to create nested properties. * - * @param name - * @param owningType + * @param name must not be {@literal null} or empty. + * @param owningType must not be {@literal null}. * @param toTraverse */ PropertyPath(String name, TypeInformation owningType, String toTraverse) { @@ -239,7 +239,14 @@ public class PropertyPath implements Iterable { return from(source, ClassTypeInformation.from(type)); } - private static PropertyPath from(String source, TypeInformation type) { + /** + * Extracts the {@link PropertyPath} chain from the given source {@link String} and {@link TypeInformation}. + * + * @param source must not be {@literal null}. + * @param type + * @return + */ + public static PropertyPath from(String source, TypeInformation type) { List iteratorSource = new ArrayList(); Matcher matcher = SPLITTER.matcher("_" + source); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index 9253c6d8e..d68a1ab7d 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 by the original author(s). + * Copyright 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ import org.springframework.util.StringUtils; */ public class BasicPersistentEntity> implements MutablePersistentEntity { - private final PreferredConstructor preferredConstructor; + private final PreferredConstructor constructor; private final TypeInformation information; private final Set

properties; private final Set> associations; @@ -65,20 +65,30 @@ public class BasicPersistentEntity> implement * @param comparator */ public BasicPersistentEntity(TypeInformation information, Comparator

comparator) { + Assert.notNull(information); + this.information = information; - this.preferredConstructor = new PreferredConstructorDiscoverer(information).getConstructor(); this.properties = comparator == null ? new HashSet

() : new TreeSet

(comparator); + this.constructor = new PreferredConstructorDiscoverer(information, this).getConstructor(); this.associations = comparator == null ? new HashSet>() : new TreeSet>( new AssociationComparator

(comparator)); } - + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor() + */ + public PreferredConstructor getPersistenceConstructor() { + return constructor; + } + /* * (non-Javadoc) - * @see org.springframework.data.mapping.PersistentEntity#getPreferredConstructor() + * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty) */ - public PreferredConstructor getPreferredConstructor() { - return preferredConstructor; + public boolean isConstructorArgument(P property) { + return constructor == null ? false : constructor.isConstructorParameter(property); } /* diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java index dd99c961a..f593d3128 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/BeanWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,13 @@ */ package org.springframework.data.mapping.model; -import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.LinkedList; -import java.util.List; -import org.springframework.beans.BeanInstantiationException; -import org.springframework.beans.BeanUtils; import org.springframework.core.convert.ConversionService; 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.Assert; import org.springframework.util.ReflectionUtils; @@ -57,74 +50,12 @@ public class BeanWrapper, T> { return new BeanWrapper(bean, conversionService); } - /** - * Creates a new {@link BeanWrapper} using the given {@link PersistentEntity} and {@link ParameterValueProvider}. Will - * instantly create a bean instance using the {@link PreferredConstructor} of the {@link PersistentEntity}. - * - * @param - * @param - * @param entity - * @param provider - * @param conversionService - * @return - */ - public static , T> BeanWrapper create(E entity, - ParameterValueProvider provider, ConversionService conversionService) { - return new BeanWrapper(entity, provider, conversionService); - } - private BeanWrapper(T bean, ConversionService conversionService) { Assert.notNull(bean); this.bean = bean; this.conversionService = conversionService; } - @SuppressWarnings("unchecked") - private BeanWrapper(E entity, ParameterValueProvider provider, ConversionService conversionService) { - - this.conversionService = conversionService; - - T bean = null; - - PreferredConstructor constructor = entity.getPreferredConstructor(); - if (null == constructor) { - try { - Class clazz = entity.getType(); - if (clazz.isArray()) { - Class ctype = clazz; - int dims = 0; - while (ctype.isArray()) { - ctype = ctype.getComponentType(); - dims++; - } - bean = (T) Array.newInstance(clazz, dims); - } else { - bean = BeanUtils.instantiateClass(entity.getType()); - } - } catch (BeanInstantiationException e) { - throw new MappingInstantiationException(e.getMessage(), e); - } - - this.bean = bean; - return; - } - - List params = new LinkedList(); - if (null != provider && constructor.hasParameters()) { - for (Parameter parameter : constructor.getParameters()) { - params.add(provider.getParameterValue(parameter)); - } - } - - try { - bean = BeanUtils.instantiateClass(constructor.getConstructor(), params.toArray()); - } catch (BeanInstantiationException e) { - throw new MappingInstantiationException(e.getMessage(), e); - } - - this.bean = bean; - } - /** * Sets the given {@link PersistentProperty} to the given value. Will do type conversion if a * {@link ConversionService} is configured. Will use the accessor method of the given {@link PersistentProperty} if it diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELAwareParameterValueProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java similarity index 54% rename from spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELAwareParameterValueProvider.java rename to spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java index 73f09c9da..d2980a3ae 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELAwareParameterValueProvider.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/DefaultSpELExpressionEvaluator.java @@ -19,7 +19,6 @@ import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.Assert; /** * {@link ParameterValueProvider} implementation that evaluates the {@link Parameter}s key against @@ -27,31 +26,27 @@ import org.springframework.util.Assert; * * @author Oliver Gierke */ -public class SpELAwareParameterValueProvider implements ParameterValueProvider { +public class DefaultSpELExpressionEvaluator implements SpELExpressionEvaluator { - private final SpelExpressionParser parser; - private final EvaluationContext context; + private final Object source; + private final SpELContext factory; /** - * Creates a new {@link SpELAwareParameterValueProvider} from the given {@link SpelExpressionParser} and - * {@link EvaluationContext}. - * - * @param parser must not be {@literal null} - * @param context must not be {@literal null} + * @param parser + * @param factory */ - public SpELAwareParameterValueProvider(SpelExpressionParser parser, EvaluationContext context) { - Assert.notNull(parser); - Assert.notNull(context); - this.parser = parser; - this.context = context; + public DefaultSpELExpressionEvaluator(Object source, SpELContext factory) { + this.source = source; + this.factory = factory; } /* (non-Javadoc) - * @see org.springframework.data.mapping.model.PreferredConstructor.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.model.PreferredConstructor.Parameter) + * @see org.springframework.data.mapping.model.SpELExpressionEvaluator#evaluate(java.lang.String) */ @SuppressWarnings("unchecked") - public T getParameterValue(Parameter parameter) { - Expression expression = parser.parseExpression(parameter.getKey()); - return (T) expression.getValue(context); + public T evaluate(String expression) { + + Expression parseExpression = factory.getParser().parseExpression(expression); + return (T) parseExpression.getValue(factory.getEvaluationContext(source)); } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java index 3d1fadde1..295e5fa91 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/ParameterValueProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright (c) 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package org.springframework.data.mapping.model; -import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor.Parameter; /** @@ -23,6 +23,13 @@ import org.springframework.data.mapping.PreferredConstructor.Parameter; * * @author Oliver Gierke */ -public interface ParameterValueProvider { - T getParameterValue(PreferredConstructor.Parameter parameter); +public interface ParameterValueProvider

> { + + /** + * Returns the value to be used for the given {@link Parameter} (usually when entity instances are created). + * + * @param parameter must not be {@literal null}. + * @return + */ + T getParameterValue(Parameter parameter); } \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java new file mode 100644 index 000000000..a42895e34 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PersistentEntityParameterValueProvider.java @@ -0,0 +1,79 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.util.Assert; + +/** + * {@link ParameterValueProvider} based on a {@link PersistentEntity} to use a {@link PropertyValueProvider} to lookup + * the value of the property referenced by the given {@link Parameter}. Additionally a + * {@link DefaultSpELExpressionEvaluator} can be configured to get property value resolution trumped by a SpEL + * expression evaluation. + * + * @author Oliver Gierke + */ +public class PersistentEntityParameterValueProvider

> implements + ParameterValueProvider

{ + + private final PersistentEntity entity; + private final PropertyValueProvider

provider; + + private SpELExpressionEvaluator spELEvaluator; + + /** + * Creates a new {@link PersistentEntityParameterValueProvider} for the given {@link PersistentEntity} and + * {@link PropertyValueProvider}. + * + * @param entity must not be {@literal null}. + * @param provider must not be {@literal null}. + */ + public PersistentEntityParameterValueProvider(PersistentEntity entity, PropertyValueProvider

provider) { + + Assert.notNull(entity); + Assert.notNull(provider); + + this.entity = entity; + this.provider = provider; + } + + /** + * Configures a {@link DefaultSpELExpressionEvaluator} to evaluate the SpEL Expression the {@link Parameter} + * potentially carries. + * + * @param spELEvaluator + */ + public void setSpELEvaluator(SpELExpressionEvaluator spELEvaluator) { + this.spELEvaluator = spELEvaluator; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter) + */ + public T getParameterValue(Parameter parameter) { + + if (spELEvaluator != null && parameter.hasSpelExpression()) { + return spELEvaluator.evaluate(parameter.getSpelExpression()); + } + + P property = entity.getPersistentProperty(parameter.getName()); + + return provider.getPropertyValue(property); + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java index 137c22c7b..d8e258aef 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.List; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer; +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; @@ -31,30 +33,45 @@ import org.springframework.data.util.TypeInformation; * * @author Oliver Gierke */ -public class PreferredConstructorDiscoverer { +public class PreferredConstructorDiscoverer> { private final ParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); - private PreferredConstructor constructor; + private PreferredConstructor constructor; + /** + * Creates a new {@link PreferredConstructorDiscoverer} for the given type. + * + * @param type must not be {@literal null}. + */ public PreferredConstructorDiscoverer(Class type) { - this(ClassTypeInformation.from(type)); + this(ClassTypeInformation.from(type), null); + } + + /** + * Creates a new {@link PreferredConstructorDiscoverer} for the given {@link PersistentEntity}. + * + * @param entity must not be {@literal null}. + */ + public PreferredConstructorDiscoverer(PersistentEntity entity) { + this(entity.getTypeInformation(), entity); } /** * Creates a new {@link PreferredConstructorDiscoverer} for the given type. * - * @param owningType + * @param type must not be {@literal null}. + * @param entity */ - protected PreferredConstructorDiscoverer(TypeInformation owningType) { + protected PreferredConstructorDiscoverer(TypeInformation type, PersistentEntity entity) { boolean noArgConstructorFound = false; int numberOfArgConstructors = 0; - Class rawOwningType = owningType.getType(); + Class rawOwningType = type.getType(); for (Constructor constructor : rawOwningType.getDeclaredConstructors()) { - PreferredConstructor preferredConstructor = buildPreferredConstructor(constructor, owningType); + PreferredConstructor preferredConstructor = buildPreferredConstructor(constructor, type, entity); // Explicitly defined constructor trumps all if (preferredConstructor.isExplicitlyAnnotated()) { @@ -80,17 +97,17 @@ public class PreferredConstructorDiscoverer { } @SuppressWarnings({ "unchecked", "rawtypes" }) - private PreferredConstructor buildPreferredConstructor(Constructor constructor, - TypeInformation typeInformation) { + private PreferredConstructor buildPreferredConstructor(Constructor constructor, + TypeInformation typeInformation, PersistentEntity entity) { List> parameterTypes = typeInformation.getParameterTypes(constructor); if (parameterTypes.isEmpty()) { - return new PreferredConstructor((Constructor) constructor); + return new PreferredConstructor((Constructor) constructor); } String[] parameterNames = nameDiscoverer.getParameterNames(constructor); - Parameter[] parameters = new Parameter[parameterTypes.size()]; + Parameter[] parameters = new Parameter[parameterTypes.size()]; Annotation[][] parameterAnnotations = constructor.getParameterAnnotations(); for (int i = 0; i < parameterTypes.size(); i++) { @@ -99,13 +116,18 @@ public class PreferredConstructorDiscoverer { TypeInformation type = parameterTypes.get(i); Annotation[] annotations = parameterAnnotations[i]; - parameters[i] = new Parameter(name, type, annotations); + parameters[i] = new Parameter(name, type, annotations, entity); } - return new PreferredConstructor((Constructor) constructor, parameters); + return new PreferredConstructor((Constructor) constructor, parameters); } - public PreferredConstructor getConstructor() { + /** + * Returns the discovered {@link PreferredConstructor}. + * + * @return + */ + public PreferredConstructor getConstructor() { return constructor; } } \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PropertyValueProvider.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PropertyValueProvider.java new file mode 100644 index 000000000..8f04d98c9 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/PropertyValueProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.data.mapping.PersistentProperty; + +/** + * SPI for components to provide values for as {@link PersistentProperty}. + * + * @author Oliver Gierke + */ +public interface PropertyValueProvider

> { + + /** + * Returns a value for the given {@link PersistentProperty}. + * + * @param property will never be {@literal null}. + * @return + */ + T getPropertyValue(P property); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELContext.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELContext.java new file mode 100644 index 000000000..9773bf2fa --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELContext.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +/** + * Simple factory to create {@link SpelExpressionParser} and {@link EvaluationContext} instances. + * + * @author Oliver Gierke + */ +public class SpELContext { + + private final SpelExpressionParser parser; + private final PropertyAccessor accessor; + private final BeanFactory factory; + + /** + * Creates a new {@link SpELContext} with the given {@link PropertyAccessor}. Defaults the + * {@link SpelExpressionParser}. + * + * @param accessor + */ + public SpELContext(PropertyAccessor accessor) { + this(accessor, null, null); + } + + /** + * Creates a new {@link SpELContext} using the given {@link SpelExpressionParser} and {@link PropertyAccessor}. Will + * default the {@link SpelExpressionParser} in case the given value for it is {@literal null}. + * + * @param parser + * @param accessor + */ + public SpELContext(SpelExpressionParser parser, PropertyAccessor accessor) { + this(accessor, parser, null); + } + + /** + * Copy constructor to create a {@link SpELContext} using the given one's {@link PropertyAccessor} and + * {@link SpelExpressionParser} as well as the given {@link BeanFactory}. + * + * @param source + * @param factory + */ + public SpELContext(SpELContext source, BeanFactory factory) { + this(source.accessor, source.parser, factory); + } + + /** + * Creates a new {@link SpELContext} using the given {@link SpelExpressionParser}, {@link PropertyAccessor} and + * {@link BeanFactory}. Will default the {@link SpelExpressionParser} in case the given value for it is + * {@literal null}. + * + * @param accessor + * @param parser + * @param factory + */ + private SpELContext(PropertyAccessor accessor, SpelExpressionParser parser, BeanFactory factory) { + + this.parser = parser == null ? new SpelExpressionParser() : parser; + this.accessor = accessor; + this.factory = factory; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.SpELContext#getParser() + */ + public ExpressionParser getParser() { + return this.parser; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.SpELContext#getEvaluationContext(java.lang.Object) + */ + public EvaluationContext getEvaluationContext(Object source) { + + StandardEvaluationContext evaluationContext = new StandardEvaluationContext(source); + + if (accessor != null) { + evaluationContext.addPropertyAccessor(accessor); + } + + if (factory != null) { + evaluationContext.setBeanResolver(new BeanFactoryResolver(factory)); + } + + return evaluationContext; + } + +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java new file mode 100644 index 000000000..8b5085967 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/mapping/model/SpELExpressionEvaluator.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.mapping.model; + +/** + * SPI for components that can evaluate Spring EL expressions. + * + * @author Oliver Gierke + */ +public interface SpELExpressionEvaluator { + + /** + * Evaluates the given expression. + * + * @param expression + * @return + */ + T evaluate(String expression); +} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java new file mode 100644 index 000000000..533d622f6 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/core/support/ReflectionEntityInformation.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.repository.core.support; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; + +import org.springframework.data.annotation.Id; +import org.springframework.data.repository.core.EntityInformation; +import org.springframework.data.repository.core.support.AbstractEntityInformation; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.ReflectionUtils.FieldCallback; + +/** + * {@link EntityInformation} implementation that inspects fields for an annotation and looks up this field's value to + * retrieve the id. + * + * @author Oliver Gierke + */ +public class ReflectionEntityInformation extends AbstractEntityInformation { + + private static final Class DEFAULT_ID_ANNOTATION = Id.class; + + private Field field; + + /** + * Creates a new {@link ReflectionEntityInformation} inspecting the given domain class for a field carrying the + * {@link Id} annotation. + * + * @param domainClass must not be {@literal null}. + */ + public ReflectionEntityInformation(Class domainClass) { + this(domainClass, DEFAULT_ID_ANNOTATION); + } + + /** + * Creates a new {@link ReflectionEntityInformation} inspecting the given domain class for a field carrying the given + * annotation. + * + * @param domainClass must not be {@literal null}. + * @param annotation must not be {@literal null}. + */ + public ReflectionEntityInformation(Class domainClass, final Class annotation) { + + super(domainClass); + Assert.notNull(annotation); + + ReflectionUtils.doWithFields(domainClass, new FieldCallback() { + public void doWith(Field field) { + if (field.getAnnotation(annotation) != null) { + ReflectionEntityInformation.this.field = field; + return; + } + } + }); + + ReflectionUtils.makeAccessible(field); + + Assert.notNull(this.field, String.format("No field annotated with %s found!", annotation.toString())); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object) + */ + @SuppressWarnings("unchecked") + public ID getId(Object entity) { + return entity == null ? null : (ID) ReflectionUtils.getField(field, entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.core.EntityInformation#getIdType() + */ + @SuppressWarnings("unchecked") + public Class getIdType() { + return (Class) field.getType(); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java new file mode 100644 index 000000000..18eaf449b --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/convert/EntityInstantiatorsUnitTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.mapping.PersistentEntity; + +/** + * Unit tests for {@link EntityInstantiators}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class EntityInstantiatorsUnitTests { + + @Mock + PersistentEntity entity; + + @Mock + EntityInstantiator customInstantiator; + + @Test(expected = IllegalArgumentException.class) + public void rejectsNullFallbackInstantiator() { + new EntityInstantiators((EntityInstantiator) null); + } + + @Test + public void usesReflectionEntityInstantiatorAsDefaultFallback() { + + EntityInstantiators instantiators = new EntityInstantiators(); + assertThat(instantiators.getInstantiatorFor(entity), is((EntityInstantiator) ReflectionEntityInstantiator.INSTANCE)); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void returnsCustomInstantiatorForTypeIfRegistered() { + + when(entity.getType()).thenReturn((Class) String.class); + + Map, EntityInstantiator> customInstantiators = Collections., EntityInstantiator> singletonMap( + String.class, customInstantiator); + + EntityInstantiators instantiators = new EntityInstantiators(customInstantiators); + assertThat(instantiators.getInstantiatorFor(entity), is(customInstantiator)); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void usesCustomFallbackInstantiatorsIfConfigured() { + + when(entity.getType()).thenReturn((Class) Object.class); + + Map, EntityInstantiator> customInstantiators = Collections., EntityInstantiator> singletonMap( + String.class, ReflectionEntityInstantiator.INSTANCE); + + EntityInstantiators instantiators = new EntityInstantiators(customInstantiator, customInstantiators); + instantiators.getInstantiatorFor(entity); + + assertThat(instantiators.getInstantiatorFor(entity), is(customInstantiator)); + + when(entity.getType()).thenReturn((Class) String.class); + assertThat(instantiators.getInstantiatorFor(entity), is((EntityInstantiator) ReflectionEntityInstantiator.INSTANCE)); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java b/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java new file mode 100644 index 000000000..02b1bbe9f --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/convert/ReflectionEntityInstantiatorUnitTest.java @@ -0,0 +1,87 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.springframework.data.convert.ReflectionEntityInstantiator.*; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +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.ParameterValueProvider; +import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; + +/** + * Unit tests for {@link ReflectionEntityInstantiator}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class ReflectionEntityInstantiatorUnitTest

> { + + @Mock + PersistentEntity entity; + @Mock + ParameterValueProvider

provider; + @Mock + PreferredConstructor constructor; + @Mock + Parameter parameter; + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void instantiatesSimpleObjectCorrectly() { + + when(entity.getType()).thenReturn((Class) Object.class); + INSTANCE.createInstance(entity, provider); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void instantiatesArrayCorrectly() { + + when(entity.getType()).thenReturn((Class) String[][].class); + INSTANCE.createInstance(entity, provider); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void instantiatesTypeWithPreferredConstructorUsingParameterValueProvider() { + + PreferredConstructor constructor = new PreferredConstructorDiscoverer(Foo.class).getConstructor(); + + when(entity.getType()).thenReturn((Class) Foo.class); + when(entity.getPersistenceConstructor()).thenReturn(constructor); + + Object instance = INSTANCE.createInstance(entity, provider); + + assertTrue(instance instanceof Foo); + verify(provider, times(1)).getParameterValue((Parameter) constructor.getParameters().iterator().next()); + } + + static class Foo { + + Foo(String foo) { + + } + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java new file mode 100644 index 000000000..691830f9a --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/convert/SimpleTypeInformationMapperUnitTests.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.convert; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.TypeInformation; + +/** + * + * @author Oliver Gierke + */ +public class SimpleTypeInformationMapperUnitTests { + + @Test + @SuppressWarnings({ "rawtypes" }) + public void resolvesTypeByLoadingClass() { + + TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + TypeInformation type = mapper.resolveTypeFrom("java.lang.String"); + + TypeInformation expected = ClassTypeInformation.from(String.class); + + assertThat(type, is(expected)); + } + + @Test + public void returnsNullForNonStringKey() { + + TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + assertThat(mapper.resolveTypeFrom(new Object()), is(nullValue())); + } + + @Test + public void returnsNullForEmptyTypeKey() { + + TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + assertThat(mapper.resolveTypeFrom(""), is(nullValue())); + } + + @Test + public void returnsNullForUnloadableClass() { + + TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + assertThat(mapper.resolveTypeFrom("Foo"), is(nullValue())); + } + + @Test + public void usesFullyQualifiedClassNameAsTypeKey() { + + TypeInformationMapper mapper = new SimpleTypeInformationMapper(); + Object alias = mapper.createAliasFor(ClassTypeInformation.from(String.class)); + + assertTrue(alias instanceof String); + assertThat(alias, is((Object) String.class.getName())); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java index 0147e4e4e..11b4626a6 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/mapping/PreferredConstructorDiscovererUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 by the original author(s). + * Copyright 2011-2012 by the original author(s). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,48 +25,41 @@ import org.springframework.data.annotation.PersistenceConstructor; import org.springframework.data.mapping.PreferredConstructor.Parameter; import org.springframework.data.mapping.model.PreferredConstructorDiscoverer; - /** * Unit tests for {@link PreferredConstructorDiscoverer}. - * + * * @author Oliver Gierke */ -public class PreferredConstructorDiscovererUnitTests { +public class PreferredConstructorDiscovererUnitTests

> { @Test public void findsNoArgConstructorForClassWithoutExplicitConstructor() { - PreferredConstructorDiscoverer discoverer = - new PreferredConstructorDiscoverer( - EntityWithoutConstructor.class); - PreferredConstructor constructor = - discoverer.getConstructor(); + PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer( + EntityWithoutConstructor.class); + PreferredConstructor constructor = discoverer.getConstructor(); assertThat(constructor, is(notNullValue())); assertThat(constructor.isNoArgConstructor(), is(true)); assertThat(constructor.isExplicitlyAnnotated(), is(false)); } - @Test public void findsNoArgConstructorForClassWithMultipleConstructorsAndNoArgOne() { - PreferredConstructorDiscoverer discoverer = - new PreferredConstructorDiscoverer( - ClassWithEmptyConstructor.class); - PreferredConstructor constructor = - discoverer.getConstructor(); + PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer( + ClassWithEmptyConstructor.class); + PreferredConstructor constructor = discoverer.getConstructor(); assertThat(constructor, is(notNullValue())); assertThat(constructor.isNoArgConstructor(), is(true)); assertThat(constructor.isExplicitlyAnnotated(), is(false)); } - @Test public void doesNotThrowExceptionForMultipleConstructorsAndNoNoArgConstructorWithoutAnnotation() { - PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer( + PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer( ClassWithMultipleConstructorsWithoutEmptyOne.class); assertThat(discoverer.getConstructor(), is(nullValue())); } @@ -74,20 +67,18 @@ public class PreferredConstructorDiscovererUnitTests { @Test public void usesConstructorWithAnnotationOverEveryOther() { - PreferredConstructorDiscoverer discoverer = - new PreferredConstructorDiscoverer( - ClassWithMultipleConstructorsAndAnnotation.class); - PreferredConstructor constructor = - discoverer.getConstructor(); + PreferredConstructorDiscoverer discoverer = new PreferredConstructorDiscoverer( + ClassWithMultipleConstructorsAndAnnotation.class); + PreferredConstructor constructor = discoverer.getConstructor(); assertThat(constructor, is(notNullValue())); assertThat(constructor.isNoArgConstructor(), is(false)); assertThat(constructor.isExplicitlyAnnotated(), is(true)); assertThat(constructor.hasParameters(), is(true)); - Iterator> parameters = constructor.getParameters().iterator(); + Iterator> parameters = constructor.getParameters().iterator(); - Parameter parameter = parameters.next(); + Parameter parameter = parameters.next(); assertThat(parameter.getType().getType(), typeCompatibleWith(Long.class)); assertThat(parameters.hasNext(), is(false)); } @@ -107,7 +98,6 @@ public class PreferredConstructorDiscovererUnitTests { public ClassWithMultipleConstructorsAndEmptyOne(String value) { } - public ClassWithMultipleConstructorsAndEmptyOne() { } }