From 326a10f1bb54059356f810aa3eca1c7565b83daa Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Wed, 14 Oct 2020 11:11:59 +0200 Subject: [PATCH] extract some interfaces --- .../data/mapping/PreferredConstructor.java | 1 - .../mapping/PreferredConstructorProvider.java | 34 ++++ .../context/AbstractMappingContext.java | 22 +-- .../model/AccessorFunctionProvider.java | 54 ++++++ .../mapping/model/BasicPersistentEntity.java | 22 ++- .../mapping/model/EntityInstantiators.java | 6 +- .../model/EntiyInstantiatorProvider.java | 50 +++++ ...istentPropertyAccessorFactoryProvider.java | 35 ++++ .../model/StaticPropertyAccessorFactory.java | 38 ++-- .../data/util/AddressTypeInformation.java | 44 ++--- .../data/util/AnnotationProvider.java | 32 ++++ .../data/util/ClassTypeInformation.java | 5 +- .../org/springframework/data/util/Field.java | 179 ++++++++++++++++++ .../org/springframework/data/util/Fields.java | 76 ++++++++ .../data/util/ListTypeInformation.java | 10 +- .../org/springframework/data/util/Person.java | 15 +- .../data/util/PersonTypeInformation.java | 123 ++++++------ .../data/util/StaticTypeInformation.java | 159 ++++++++++------ .../data/util/StringTypeInformation.java | 5 + .../MappingMongoConverterUnitTests.java | 104 +++++----- .../staticmetadata/StaticMetadataTests.java | 109 +++++++++++ 21 files changed, 885 insertions(+), 238 deletions(-) create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructorProvider.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AccessorFunctionProvider.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntiyInstantiatorProvider.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentPropertyAccessorFactoryProvider.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/util/AnnotationProvider.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/util/Field.java create mode 100644 spring-data-mongodb/src/main/java/org/springframework/data/util/Fields.java create mode 100644 spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/staticmetadata/StaticMetadataTests.java diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructor.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructor.java index 5e96897e9..d064088be 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructor.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructor.java @@ -52,7 +52,6 @@ public class PreferredConstructor> { public PreferredConstructor() { this.constructor = null; this.parameters = Collections.emptyList(); - } /** diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructorProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructorProvider.java new file mode 100644 index 000000000..bcfe21367 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/PreferredConstructorProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright 2020. 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; + +import org.springframework.lang.Nullable; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public interface PreferredConstructorProvider { + + @Nullable +

> PreferredConstructor getPreferredConstructor(); + + default

> PreferredConstructor getPreferredConstructorOrDefault(PreferredConstructor fallback) { + + PreferredConstructor preferredConstructor = getPreferredConstructor(); + return preferredConstructor != null ? preferredConstructor : fallback; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java index 69823e95b..48ed43595 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/context/AbstractMappingContext.java @@ -16,16 +16,13 @@ package org.springframework.data.mapping.context; import java.beans.PropertyDescriptor; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.concurrent.locks.Lock; @@ -379,18 +376,17 @@ public abstract class AbstractMappingContext) { - // ((StaticTypeInformation)typeInformation).doWithProperties() + final E pEntity = entity; + ((StaticTypeInformation) typeInformation).doWithFields((fieldName, field) -> { - Map> properties = ((StaticTypeInformation) typeInformation).getProperties(); - Map> annotations = ((StaticTypeInformation) typeInformation).getPropertyAnnotations(); - for (Entry> entry : properties.entrySet()) { + System.out.println("Creating PersistentProperty for " + fieldName + " via static configuration."); + P target = createPersistentProperty( + Property.of(field.getTypeInformation(), fieldName, field.getAnnotations()), pEntity, simpleTypeHolder); + pEntity.addPersistentProperty(target); - P target = createPersistentProperty(Property.of(entry.getValue(), entry.getKey(), annotations.get(entry.getKey())), entity, simpleTypeHolder); - entity.addPersistentProperty(target); - - } - entity.setPersistentPropertyAccessorFactory(new StaticPropertyAccessorFactory()); - return Optional.of(entity); + }); + pEntity.setPersistentPropertyAccessorFactory(StaticPropertyAccessorFactory.instance()); + return Optional.of(pEntity); } PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AccessorFunctionProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AccessorFunctionProvider.java new file mode 100644 index 000000000..e49b6f789 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/AccessorFunctionProvider.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020. 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. + */ + +/* + * Copyright 2020 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 java.util.function.BiFunction; +import java.util.function.Function; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public interface AccessorFunctionProvider { + + default boolean hasSetFunctionFor(String fieldName) { + return getSetFunctionFor(fieldName) != null; + } + + default boolean hasGetFunctionFor(String fieldName) { + return getGetFunctionFor(fieldName) != null; + } + + BiFunction getSetFunctionFor(String fieldName); + + Function getGetFunctionFor(String fieldName); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index 3a35055e3..d70046b01 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -37,8 +37,8 @@ import org.springframework.data.mapping.*; import org.springframework.data.spel.EvaluationContextProvider; import org.springframework.data.support.IsNewStrategy; import org.springframework.data.support.PersistableIsNewStrategy; +import org.springframework.data.util.AnnotationProvider; import org.springframework.data.util.Lazy; -import org.springframework.data.util.StaticTypeInformation; import org.springframework.data.util.TypeInformation; import org.springframework.expression.EvaluationContext; import org.springframework.lang.Nullable; @@ -109,14 +109,30 @@ public class BasicPersistentEntity> implement this.properties = new ArrayList<>(); this.persistentPropertiesCache = new ArrayList<>(); this.comparator = comparator; - this.constructor = information instanceof StaticTypeInformation ? ((StaticTypeInformation)information).getPreferredConstructor() : PreferredConstructorDiscoverer.discover(this); + + this.constructor = information instanceof PreferredConstructorProvider + ? ((PreferredConstructorProvider) information).getPreferredConstructor() + : PreferredConstructorDiscoverer.discover(this); + this.associations = comparator == null ? new HashSet<>() : new TreeSet<>(new AssociationComparator<>(comparator)); this.propertyCache = new HashMap<>(16, 1f); + this.annotationCache = new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK); + if(information instanceof AnnotationProvider) { + for(Annotation annotation : ((AnnotationProvider)information).getAnnotations()) { + annotationCache.put(annotation.annotationType(), Optional.of(annotation)); + } + } + + this.propertyAnnotationCache = CollectionUtils .toMultiValueMap(new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK)); - this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE; + + this.propertyAccessorFactory = information instanceof PersistentPropertyAccessorFactoryProvider + ? ((PersistentPropertyAccessorFactoryProvider) information).getPersistentPropertyAccessorFactory() + : BeanWrapperPropertyAccessorFactory.INSTANCE; + this.typeAlias = Lazy.of(() -> getAliasFromAnnotation(getType())); this.isNewStrategy = Lazy.of(() -> Persistable.class.isAssignableFrom(information.getType()) // ? PersistableIsNewStrategy.INSTANCE diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java index bc67e50cb..05180dd74 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntityInstantiators.java @@ -19,7 +19,6 @@ import java.util.Collections; import java.util.Map; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.util.StaticTypeInformation; import org.springframework.util.Assert; /** @@ -92,9 +91,8 @@ public class EntityInstantiators { if (!customInstantiators.containsKey(type)) { - if(entity.getTypeInformation() instanceof StaticTypeInformation) { - EntityInstantiator instantiator = ((StaticTypeInformation)entity.getTypeInformation()).getInstantiator(); - return instantiator != null ? instantiator : fallback; + if (entity.getTypeInformation() instanceof EntiyInstantiatorProvider) { + return ((EntiyInstantiatorProvider) entity.getTypeInformation()).getEntiyInstantiatorOrDefault(fallback); } return fallback; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntiyInstantiatorProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntiyInstantiatorProvider.java new file mode 100644 index 000000000..a201d8bf2 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/EntiyInstantiatorProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2020. 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. + */ + +/* + * Copyright 2020 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.lang.Nullable; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public interface EntiyInstantiatorProvider { + + @Nullable + EntityInstantiator getEntityInstantiator(); + + default EntityInstantiator getEntiyInstantiatorOrDefault(EntityInstantiator fallback) { + + EntityInstantiator entityInstantiator = getEntityInstantiator(); + return entityInstantiator != null ? entityInstantiator : fallback; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentPropertyAccessorFactoryProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentPropertyAccessorFactoryProvider.java new file mode 100644 index 000000000..6543377bb --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/PersistentPropertyAccessorFactoryProvider.java @@ -0,0 +1,35 @@ +/* + * Copyright 2020. 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.lang.Nullable; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public interface PersistentPropertyAccessorFactoryProvider { + + @Nullable + PersistentPropertyAccessorFactory getPersistentPropertyAccessorFactory(); + + default PersistentPropertyAccessorFactory getPersistentPropertyAccessorFactoryOrDefault( + PersistentPropertyAccessorFactory fallback) { + + PersistentPropertyAccessorFactory factory = getPersistentPropertyAccessorFactory(); + return factory != null ? factory : fallback; + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/StaticPropertyAccessorFactory.java b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/StaticPropertyAccessorFactory.java index a579d72a0..70d650466 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/StaticPropertyAccessorFactory.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mapping/model/StaticPropertyAccessorFactory.java @@ -31,13 +31,9 @@ */ package org.springframework.data.mapping.model; -import java.util.function.BiFunction; -import java.util.function.Function; - import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.util.StaticTypeInformation; import org.springframework.lang.Nullable; /** @@ -46,13 +42,21 @@ import org.springframework.lang.Nullable; */ public class StaticPropertyAccessorFactory implements PersistentPropertyAccessorFactory { + private static final StaticPropertyAccessorFactory INSTANCE = new StaticPropertyAccessorFactory(); + + public static StaticPropertyAccessorFactory instance() { + return INSTANCE; + } + /* * (non-Javadoc) * @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object) */ @Override public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity entity, T bean) { - return new StaticPropertyAccessor<>((StaticTypeInformation) entity.getTypeInformation(), bean); + + System.out.println("Obtaining static property acessor for entity " + entity.getName()); + return new StaticPropertyAccessor<>((AccessorFunctionProvider) entity.getTypeInformation(), bean); } /* @@ -62,7 +66,7 @@ public class StaticPropertyAccessorFactory implements PersistentPropertyAccessor @Override public boolean isSupported(PersistentEntity entity) { - boolean isStaticTypedEntity = entity.getTypeInformation() instanceof StaticTypeInformation; + boolean isStaticTypedEntity = entity.getTypeInformation() instanceof AccessorFunctionProvider; System.out.println(entity.getName() + " isStaticTypedEntity: " + isStaticTypedEntity); return isStaticTypedEntity; } @@ -70,32 +74,36 @@ public class StaticPropertyAccessorFactory implements PersistentPropertyAccessor static class StaticPropertyAccessor implements PersistentPropertyAccessor { T bean; - StaticTypeInformation typeInformation; + AccessorFunctionProvider accessorFunctionProvider; - public StaticPropertyAccessor(StaticTypeInformation typeInformation, T bean) { + public StaticPropertyAccessor(AccessorFunctionProvider accessorFunctionProvider, T bean) { this.bean = bean; - this.typeInformation = typeInformation; + this.accessorFunctionProvider = accessorFunctionProvider; } @Override public void setProperty(PersistentProperty property, @Nullable Object value) { - BiFunction setFunction = typeInformation.getSetter().get(property.getName()); - if (setFunction == null) { + if (!accessorFunctionProvider.hasSetFunctionFor(property.getName())) { return; } - this.bean = setFunction.apply(bean, value); + + this.bean = accessorFunctionProvider.getSetFunctionFor(property.getName()).apply(bean, value); + System.out.println( + "setting value " + value + " via setter function for " + property.getName() + " resulting in " + bean); } @Nullable @Override public Object getProperty(PersistentProperty property) { - Function getFunction = typeInformation.getGetter().get(property.getName()); - if (getFunction == null) { + if (!accessorFunctionProvider.hasGetFunctionFor(property.getName())) { return null; } - return getFunction.apply(bean); + + Object value = accessorFunctionProvider.getGetFunctionFor(property.getName()).apply(bean); + System.out.println("obtaining value " + value + " from getter function for " + property.getName()); + return value; } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/AddressTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/AddressTypeInformation.java index 23e7ce2d7..0445c666f 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/AddressTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/AddressTypeInformation.java @@ -32,10 +32,6 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; @@ -50,40 +46,21 @@ import org.springframework.data.mapping.model.ParameterValueProvider; */ public class AddressTypeInformation extends StaticTypeInformation

{ - public AddressTypeInformation() { + private static final AddressTypeInformation INSTANCE = new AddressTypeInformation(); + + private AddressTypeInformation() { super(Address.class); } - @Override - protected Map> computePropertiesMap() { - - Map> properties = new LinkedHashMap<>(); - properties.put("city", new StringTypeInformation()); - properties.put("street", new StringTypeInformation()); - return properties; + public static AddressTypeInformation instance() { + return INSTANCE; } @Override - protected Map> computeGetter() { - Map> getters = new LinkedHashMap<>(); - getters.put("city", Address::getCity); - getters.put("street", Address::getStreet); + protected void computeFields() { - return getters; - } - - @Override - protected Map> computeSetter() { - Map> setter = new LinkedHashMap<>(); -// setter.put("city", (bean, id) -> { -// bean.setCity((String) id); -// return bean; -// }); -// setter.put("street", (bean, id) -> { -// bean.setStreet((String) id); -// return bean; -// }); - return setter; + addField(Field.
string("city").getter(Address::getCity)); + addField(Field.
string("street").getter(Address::getStreet)); } @Override @@ -99,7 +76,10 @@ public class AddressTypeInformation extends StaticTypeInformation
{ String street = (String) provider .getParameterValue(new Parameter("street", new StringTypeInformation(), new Annotation[] {}, entity)); - return (T) new Address(city, street); + T address = (T) new Address(city, street); + System.out.println("Created new Address instance via constructor using values (" + city + ", " + street + + ") resulting in " + address); + return address; } }; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/AnnotationProvider.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/AnnotationProvider.java new file mode 100644 index 000000000..86441cf2e --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/AnnotationProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020. 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.util; + +import java.lang.annotation.Annotation; +import java.util.List; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public interface AnnotationProvider { + + List getAnnotations(); + + boolean hasAnnotation(Class annotationType); + + List findAnnotation(Class annotation); +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/ClassTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/ClassTypeInformation.java index 6ea0fdc05..75b4d9dd2 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/ClassTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/ClassTypeInformation.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.springframework.core.GenericTypeResolver; import org.springframework.util.Assert; @@ -48,8 +49,8 @@ public class ClassTypeInformation extends TypeDiscoverer { public static final ClassTypeInformation MAP = new ClassTypeInformation(Map.class); public static final ClassTypeInformation OBJECT = new ClassTypeInformation(Object.class); - private static final Map, ClassTypeInformation> cache = new ConcurrentReferenceHashMap<>(64, - ReferenceType.WEAK); + // cannot use reference hash map cause static type information might not be referenced from outside and get discarded + private static final Map, ClassTypeInformation> cache = new ConcurrentHashMap<>(); static { Arrays.asList(COLLECTION, LIST, SET, MAP, OBJECT).forEach(it -> cache.put(it.getType(), it)); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/Field.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/Field.java new file mode 100644 index 000000000..097ccc755 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/Field.java @@ -0,0 +1,179 @@ +/* + * Copyright 2020. 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. + */ + +/* + * Copyright 2020 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.util; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public class Field implements AnnotationProvider { + + @Nullable Class owner; + String propertyName; + + TypeInformation typeInformation; + @Nullable TypeInformation componentType; + @Nullable TypeInformation keyType; + + MultiValueMap, Annotation> annotations; + + @Nullable Function getterFunction; + @Nullable BiFunction setterFunction; + + public Field(String propertyName, TypeInformation propertyTypeInformation) { + + this.propertyName = propertyName; + this.typeInformation = propertyTypeInformation; + this.annotations = new LinkedMultiValueMap<>(); + } + + public static Field simple(Class type, String propertyName) { + + if (type == String.class) { + return (Field) string(propertyName); + } + + throw new IllegalArgumentException("Unknown simple type: " + type); + } + + public static Field string(String propertyName) { + return new Field<>(propertyName, StringTypeInformation.instance()); + } + + public static Field int64(String propertyName) { + return new Field<>(propertyName, StaticTypeInformation.from(Long.class)); + } + + public static Field int32(String propertyName) { + return new Field<>(propertyName, StaticTypeInformation.from(Integer.class)); + } + + public static Field type(String propertyName, TypeInformation type) { + return new Field<>(propertyName, type); + } + + public Field annotation(Annotation annotation) { + + annotations.add(annotation.annotationType(), annotation); + return this; + } + + public Field wither(BiFunction setterFunction) { + + this.setterFunction = setterFunction; + return this; + } + + public Field setter(BiConsumer setterFunction) { + + return wither((o, t) -> { + + setterFunction.accept(o, t); + return o; + }); + } + + public Field getter(Function getterFunction) { + + this.getterFunction = getterFunction; + return this; + } + + public Field valueType(TypeInformation valueTypeInformation) { + this.componentType = valueTypeInformation; + return this; + } + + Field owner(Class owner) { + + this.owner = owner; + return this; + } + + public TypeInformation getValueType() { + return componentType != null ? componentType : typeInformation; + } + + public String getFieldName() { + return propertyName; + } + + public TypeInformation getTypeInformation() { + return typeInformation; + } + + public boolean hasSetter() { + return setterFunction != null; + } + + public boolean hasGetter() { + return getterFunction != null; + } + + public BiFunction getSetter() { + return setterFunction; + } + + @Nullable + public Function getGetter() { + return getterFunction; + } + + @Override + public List getAnnotations() { + List all = new ArrayList<>(); + annotations.values().forEach(all::addAll); + return all; + } + + @Override + public boolean hasAnnotation(Class annotationType) { + return annotations.containsKey(annotationType); + } + + @Override + public List findAnnotation(Class annotation) { + return (List) annotations.getOrDefault(annotation, Collections.emptyList()); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/Fields.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/Fields.java new file mode 100644 index 000000000..6f8995ce8 --- /dev/null +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/Fields.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020. 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. + */ + +/* + * Copyright 2020 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.util; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public class Fields implements Iterable> { + + private final Class owner; + private final Map> fields; + + public Fields(Class owner) { + + this.owner = owner; + this.fields = new LinkedHashMap<>(); + } + + public Fields add(Field field) { + + this.fields.put(field.getFieldName(), field.owner(owner)); + return this; + } + + public boolean hasField(String fieldName) { + return this.fields.containsKey(fieldName); + } + + public Field getField(String fieldName) { + return (Field) this.fields.get(fieldName); + } + + public void doWithFields(BiConsumer> consumer) { + fields.forEach(consumer); + } + + @Override + public Iterator> iterator() { + return fields.values().iterator(); + } +} diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/ListTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/ListTypeInformation.java index 49e09019a..064e6e5ff 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/ListTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/ListTypeInformation.java @@ -37,10 +37,14 @@ import java.util.List; * @author Christoph Strobl * @since 2020/10 */ -public class ListTypeInformation extends StaticTypeInformation { +public class ListTypeInformation extends StaticTypeInformation> { - public ListTypeInformation(TypeInformation componentType) { - super(List.class, componentType, null); + public ListTypeInformation(TypeInformation componentType) { + super((Class) List.class, componentType, null); + } + + public static ListTypeInformation listOf(TypeInformation componentType) { + return new ListTypeInformation<>(componentType); } @Override diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/Person.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/Person.java index 831e6db75..d1165ba1a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/Person.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/Person.java @@ -52,6 +52,15 @@ public class Person { this.lastname = lastname; } + private Person(long id, String firstname, String lastname, int age, Address address, List nicknames) { + this.id = id; + this.firstname = firstname; + this.lastname = lastname; + this.age = age; + this.address = address; + this.nicknames = nicknames; + } + public String getFirstname() { return firstname; } @@ -72,8 +81,10 @@ public class Person { return id; } - public void setId(long id) { - id = id; + public Person withId(long id) { + + return new Person(id, firstname, lastname, age, address, nicknames); + } public Address getAddress() { diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/PersonTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/PersonTypeInformation.java index 4d31f0949..004450b27 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/PersonTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/PersonTypeInformation.java @@ -32,20 +32,16 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; -import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Function; +import org.springframework.data.annotation.Id; 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.EntityInstantiator; import org.springframework.data.mapping.model.ParameterValueProvider; -import org.springframework.data.mongodb.core.mapping.Field; +import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.FieldType; /** @@ -54,63 +50,63 @@ import org.springframework.data.mongodb.core.mapping.FieldType; */ public class PersonTypeInformation extends StaticTypeInformation { - public PersonTypeInformation() { + private static final PersonTypeInformation INSTANCE = new PersonTypeInformation(); + + private PersonTypeInformation() { super(Person.class); } - @Override - protected Map> computePropertiesMap() { - - LinkedHashMap> properties = new LinkedHashMap<>(); - properties.put("firstname", new StringTypeInformation()); - properties.put("lastname", new StringTypeInformation()); - properties.put("id", new StaticTypeInformation<>(Long.class)); - properties.put("age", new StaticTypeInformation<>(int.class)); - properties.put("address", new AddressTypeInformation()); - properties.put("nicknames", new ListTypeInformation(new StringTypeInformation())); - - return properties; + public static PersonTypeInformation instance() { + return INSTANCE; } @Override - protected Map> computeSetter() { - - Map> setter = new LinkedHashMap<>(); - setter.put("id", (bean, id) -> { - bean.setId((Long) id); - return bean; - }); - setter.put("age", (bean, id) -> { - bean.setAge((int) id); - return bean; - }); - // setter.put("firstname", (bean, id) -> {bean.setFirstname((String)id); return bean;}); - // setter.put("lastname", (bean, id) -> {bean.setLastname((String)id); return bean;}); - setter.put("address", (bean, id) -> { - bean.setAddress((Address) id); - return bean; - }); - setter.put("nicknames", (bean, id) -> { - bean.setNicknames((List) id); - return bean; - }); - - return setter; + protected void computeFields() { + + addField( + Field. int64("id").getter(Person::getId).wither((bean, id) -> bean.withId(id)).annotation(new Id() { + @Override + public Class annotationType() { + return Id.class; + } + })); + addField(Field. string("firstname").getter(Person::getFirstname).annotation(atFieldOnFirstname())); + addField(Field. string("lastname").getter(Person::getLastname)); + addField(Field. int32("age").getter(Person::getAge).setter(Person::setAge)); + addField(Field. type("address", AddressTypeInformation.instance()).getter(Person::getAddress) + .setter(Person::setAddress)); + addField(Field.> type("nicknames", new ListTypeInformation<>(StringTypeInformation.instance())) + .getter(Person::getNicknames).setter(Person::setNicknames)); } @Override - protected Map> computeGetter() { + protected void computeAnnotations() { + addAnnotation(new Document() { + @Override + public Class annotationType() { + return Document.class; + } - Map> getter = new LinkedHashMap<>(); + @Override + public String value() { + return collection(); + } - getter.put("firstname", Person::getFirstname); - getter.put("lastname", Person::getLastname); - getter.put("id", Person::getId); - getter.put("address", Person::getAddress); - getter.put("nicknames", Person::getNicknames); - getter.put("age", Person::getAge); + @Override + public String collection() { + return "star-wars"; + } + + @Override + public String language() { + return ""; + } - return getter; + @Override + public String collation() { + return ""; + } + }); } @Override @@ -122,12 +118,15 @@ public class PersonTypeInformation extends StaticTypeInformation { public , P extends PersistentProperty

> T createInstance(E entity, ParameterValueProvider

provider) { - String firstname = (String) provider - .getParameterValue(new Parameter("firstname", new StringTypeInformation(), new Annotation[] {}, entity)); - String lastname = (String) provider - .getParameterValue(new Parameter("lastname", new StringTypeInformation(), new Annotation[] {}, entity)); + String firstname = (String) provider.getParameterValue( + new Parameter("firstname", StringTypeInformation.instance(), new Annotation[] {}, entity)); + String lastname = (String) provider.getParameterValue( + new Parameter("lastname", StringTypeInformation.instance(), new Annotation[] {}, entity)); - return (T) new Person(firstname, lastname); + T person = (T) new Person(firstname, lastname); + System.out.println("Created new Person instance via constructor using values (" + firstname + ", " + lastname + + ") resulting in " + person); + return person; } }; } @@ -137,15 +136,13 @@ public class PersonTypeInformation extends StaticTypeInformation { return StaticPreferredConstructor.of("firstname", "lastname"); } - @Override - protected Map> computePropertyAnnotations() { + Annotation atFieldOnFirstname() { - Map> annotationMap = new LinkedHashMap<>(); - annotationMap.put("firstname", Collections.singletonList(new Field() { + return new org.springframework.data.mongodb.core.mapping.Field() { @Override public Class annotationType() { - return Field.class; + return org.springframework.data.mongodb.core.mapping.Field.class; } @Override @@ -167,8 +164,6 @@ public class PersonTypeInformation extends StaticTypeInformation { public FieldType targetType() { return FieldType.IMPLICIT; } - })); - - return annotationMap; + }; } } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/StaticTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/StaticTypeInformation.java index 80d42f0bf..1339c1ac8 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/StaticTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/StaticTypeInformation.java @@ -34,24 +34,35 @@ package org.springframework.data.util; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.Map; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PreferredConstructor; +import org.springframework.data.mapping.PreferredConstructorProvider; +import org.springframework.data.mapping.model.AccessorFunctionProvider; import org.springframework.data.mapping.model.EntityInstantiator; +import org.springframework.data.mapping.model.EntiyInstantiatorProvider; +import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory; +import org.springframework.data.mapping.model.PersistentPropertyAccessorFactoryProvider; +import org.springframework.data.mapping.model.StaticPropertyAccessorFactory; import org.springframework.lang.Nullable; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.ObjectUtils; /** * @author Christoph Strobl * @since 2020/10 */ -public class StaticTypeInformation extends ClassTypeInformation { +public class StaticTypeInformation extends ClassTypeInformation + implements AnnotationProvider, EntiyInstantiatorProvider, PreferredConstructorProvider, + PersistentPropertyAccessorFactoryProvider, AccessorFunctionProvider { private final Class type; @@ -60,13 +71,12 @@ public class StaticTypeInformation extends ClassTypeInformation { private StaticTypeInformation superTypeInformation; private List> typeArguments; - private final Map> properties; - private final Map> setter; - private final Map> getter; - private final Map> propertyAnnotations; + private MultiValueMap, Annotation> annotations; + + private final Fields fields; private EntityInstantiator instantiator; - private PreferredConstructor preferredConstructor; + private PreferredConstructor preferredConstructor; public StaticTypeInformation(Class type) { this(type, null, null); @@ -79,18 +89,19 @@ public class StaticTypeInformation extends ClassTypeInformation { this.type = type; this.componentType = componentType; this.keyType = keyType; - this.properties = computePropertiesMap(); this.typeArguments = computeTypeArguments(); this.instantiator = computeEntityInstantiator(); - this.setter = computeSetter(); - this.getter = computeGetter(); - this.preferredConstructor = computePreferredConstructor(); - this.propertyAnnotations = computePropertyAnnotations(); + this.preferredConstructor = computePreferredConstructor(); + this.annotations = new LinkedMultiValueMap<>(); + this.fields = new Fields(type); + + computeFields(); + computeAnnotations(); } - protected Map> computePropertiesMap() { - return Collections.emptyMap(); - }; + protected void addField(Field field) { + this.fields.add(field); + } protected List> computeTypeArguments() { return Collections.emptyList(); @@ -100,44 +111,29 @@ public class StaticTypeInformation extends ClassTypeInformation { return null; } - protected PreferredConstructor computePreferredConstructor() { + protected PreferredConstructor computePreferredConstructor() { return null; } - public PreferredConstructor getPreferredConstructor() { + @Override + public PreferredConstructor getPreferredConstructor() { return preferredConstructor; } - protected Map> computeSetter() { - return Collections.emptyMap(); - } - - protected Map> computeGetter() { - return Collections.emptyMap(); - } - - protected Map> computePropertyAnnotations() { - return Collections.emptyMap(); + protected void computeFields() { + // } - public Map> getProperties() { - return properties; - } + protected void computeAnnotations() { - public Map> getSetter() { - return setter; } - public Map> getGetter() { - return getter; + protected void addAnnotation(Annotation annotation) { + this.annotations.add(annotation.annotationType(), annotation); } - public EntityInstantiator getInstantiator() { - return instantiator; - } - - public Map> getPropertyAnnotations() { - return propertyAnnotations; + public void doWithFields(BiConsumer> consumer) { + fields.doWithFields(consumer); } @Override @@ -148,7 +144,12 @@ public class StaticTypeInformation extends ClassTypeInformation { @Nullable @Override public TypeInformation getProperty(String property) { - return properties.get(property); + + if (!fields.hasField(property)) { + return null; + } + + return fields.getField(property).getTypeInformation(); } @Override @@ -216,9 +217,12 @@ public class StaticTypeInformation extends ClassTypeInformation { @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; StaticTypeInformation that = (StaticTypeInformation) o; @@ -237,13 +241,7 @@ public class StaticTypeInformation extends ClassTypeInformation { if (!ObjectUtils.nullSafeEquals(typeArguments, that.typeArguments)) { return false; } - if (!ObjectUtils.nullSafeEquals(properties, that.properties)) { - return false; - } - if (!ObjectUtils.nullSafeEquals(setter, that.setter)) { - return false; - } - if (!ObjectUtils.nullSafeEquals(getter, that.getter)) { + if (!ObjectUtils.nullSafeEquals(fields, that.fields)) { return false; } return ObjectUtils.nullSafeEquals(instantiator, that.instantiator); @@ -257,13 +255,64 @@ public class StaticTypeInformation extends ClassTypeInformation { result = 31 * result + ObjectUtils.nullSafeHashCode(keyType); result = 31 * result + ObjectUtils.nullSafeHashCode(superTypeInformation); result = 31 * result + ObjectUtils.nullSafeHashCode(typeArguments); - result = 31 * result + ObjectUtils.nullSafeHashCode(properties); - result = 31 * result + ObjectUtils.nullSafeHashCode(setter); - result = 31 * result + ObjectUtils.nullSafeHashCode(getter); + result = 31 * result + ObjectUtils.nullSafeHashCode(fields); + result = 31 * result + ObjectUtils.nullSafeHashCode(instantiator); return result; } + @Nullable + @Override + public EntityInstantiator getEntityInstantiator() { + return instantiator; + } + + @Override + public List getAnnotations() { + + List all = new ArrayList<>(); + annotations.values().forEach(all::addAll); + return all; + } + + @Override + public boolean hasAnnotation(Class annotationType) { + return annotations.containsKey(annotationType); + } + + @Override + public List findAnnotation(Class annotation) { + return (List) annotations.getOrDefault(annotation, Collections.emptyList()); + } + + @Nullable + @Override + public PersistentPropertyAccessorFactory getPersistentPropertyAccessorFactory() { + return StaticPropertyAccessorFactory.instance(); + } + + @Override + public BiFunction getSetFunctionFor(String fieldName) { + + Field entityField = fields.getField(fieldName); + if (entityField == null) { + return null; + } + + return entityField.getSetter(); + } + + @Override + public Function getGetFunctionFor(String fieldName) { + + Field entityField = fields.getField(fieldName); + if (entityField == null) { + return null; + } + + return entityField.getGetter(); + } + public static class StaticPreferredConstructor extends PreferredConstructor { private List args; @@ -279,7 +328,7 @@ public class StaticTypeInformation extends ClassTypeInformation { @Override public boolean isConstructorParameter(PersistentProperty property) { - if(args.contains(property.getName())) { + if (args.contains(property.getName())) { return true; } diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/util/StringTypeInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/util/StringTypeInformation.java index 30378263c..2266e2e08 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/util/StringTypeInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/util/StringTypeInformation.java @@ -37,8 +37,13 @@ package org.springframework.data.util; */ public class StringTypeInformation extends StaticTypeInformation { + private static final StringTypeInformation INSTANCE = new StringTypeInformation(); + public StringTypeInformation() { super(String.class); } + public static TypeInformation instance() { + return INSTANCE; + } } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java index c08f62341..7c8d9e7ee 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/convert/MappingMongoConverterUnitTests.java @@ -71,6 +71,7 @@ import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.N import org.springframework.data.mongodb.core.convert.DocumentAccessorUnitTests.ProjectingType; import org.springframework.data.mongodb.core.convert.MappingMongoConverterUnitTests.ClassWithMapUsingEnumAsKey.FooBarEnum; import org.springframework.data.mongodb.core.geo.Sphere; +import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity; import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Field; import org.springframework.data.mongodb.core.mapping.FieldType; @@ -81,6 +82,7 @@ import org.springframework.data.mongodb.core.mapping.TextScore; import org.springframework.data.mongodb.core.mapping.event.AfterConvertCallback; import org.springframework.data.util.AddressTypeInformation; import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Person; import org.springframework.data.util.PersonTypeInformation; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.StopWatch; @@ -2183,46 +2185,46 @@ public class MappingMongoConverterUnitTests { assertThat(((LinkedHashMap) result.get("cluster")).get("_id")).isEqualTo(100L); } - @Test - public void perf1() { - - ClassTypeInformation.warmCache(new PersonTypeInformation(), new AddressTypeInformation()); - - MongoMappingContext mappingContext = new MongoMappingContext(); - mappingContext.setInitialEntitySet(new LinkedHashSet<>( - Arrays.asList(org.springframework.data.util.Person.class, org.springframework.data.util.Address.class))); - mappingContext.initialize(); - - MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); - - org.springframework.data.util.Person source = new org.springframework.data.util.Person("spring", "data"); - source.setAddress(new org.springframework.data.util.Address("the city", "never sleeps")); - source.setAge(10); - source.setId(9876); - source.setNicknames(Arrays.asList("tick", "trick", "track")); - - StopWatch stopWatch = new StopWatch(); - - List sources = new ArrayList<>(); - stopWatch.start("write"); - for (int i = 0; i < 10000; i++) { - - org.bson.Document targetDocument = new org.bson.Document(); - converter.write(source, targetDocument); - - sources.add(targetDocument); - } - stopWatch.stop(); - - stopWatch.start("read"); - for (org.bson.Document sourceDoc : sources) { - assertThat(converter.read(org.springframework.data.util.Person.class, sourceDoc)).isEqualTo(source); - } - stopWatch.stop(); - - System.out.println(stopWatch.prettyPrint()); - - } +// @Test +// public void perf1() { +// +// ClassTypeInformation.warmCache(PersonTypeInformation.instance(), AddressTypeInformation.instance()); +// +// MongoMappingContext mappingContext = new MongoMappingContext(); +// mappingContext.setInitialEntitySet(new LinkedHashSet<>( +// Arrays.asList(org.springframework.data.util.Person.class, org.springframework.data.util.Address.class))); +// mappingContext.initialize(); +// +// MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); +// +// org.springframework.data.util.Person source = new org.springframework.data.util.Person("spring", "data"); +// source.setAddress(new org.springframework.data.util.Address("the city", "never sleeps")); +// source.setAge(10); +// source = source.withId(9876); +// source.setNicknames(Arrays.asList("tick", "trick", "track")); +// +// StopWatch stopWatch = new StopWatch(); +// +// List sources = new ArrayList<>(); +// stopWatch.start("write"); +// for (int i = 0; i < 10000; i++) { +// +// org.bson.Document targetDocument = new org.bson.Document(); +// converter.write(source, targetDocument); +// +// sources.add(targetDocument); +// } +// stopWatch.stop(); +// +// stopWatch.start("read"); +// for (org.bson.Document sourceDoc : sources) { +// assertThat(converter.read(org.springframework.data.util.Person.class, sourceDoc)).isEqualTo(source); +// } +// stopWatch.stop(); +// +// System.out.println(stopWatch.prettyPrint()); +// +// } // public void perf2() { // @@ -2244,9 +2246,9 @@ public class MappingMongoConverterUnitTests { // } @Test - public void xxx() { + public void staticEntityMetadata() { - ClassTypeInformation.warmCache(new PersonTypeInformation(), new AddressTypeInformation()); + ClassTypeInformation.warmCache(PersonTypeInformation.instance(), AddressTypeInformation.instance()); MongoMappingContext mappingContext = new MongoMappingContext(); mappingContext.setInitialEntitySet(new LinkedHashSet<>( @@ -2256,19 +2258,33 @@ public class MappingMongoConverterUnitTests { org.springframework.data.util.Person source = new org.springframework.data.util.Person("spring", "data"); source.setAddress(new org.springframework.data.util.Address("the city", "never sleeps")); source.setAge(10); - source.setId(9876); + source = source.withId(9876); source.setNicknames(Arrays.asList("tick", "trick", "track")); MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); org.bson.Document targetDocument = new org.bson.Document(); + System.out.println(); + System.out.println("------ WRITE -------"); converter.write(source, targetDocument); + System.out.println(); - System.out.println("target: " + targetDocument); + System.out.println("targetDocument: " + targetDocument); + System.out.println(); + System.out.println("------ READ -------"); + assertThat(targetDocument).containsEntry("_id", 9876L); + assertThat(targetDocument).containsEntry("first-name", "spring"); + assertThat(targetDocument).containsEntry("address", + new org.bson.Document("city", "the city").append("street", "never sleeps")); + assertThat(targetDocument).containsEntry("nicknames", Arrays.asList("tick", "trick", "track")); org.springframework.data.util.Person targetEntity = converter.read(org.springframework.data.util.Person.class, targetDocument); + System.out.println(); System.out.println("targetEntity: " + targetEntity); + assertThat(targetEntity).isEqualTo(source); + BasicMongoPersistentEntity entity = mappingContext.getPersistentEntity(org.springframework.data.util.Person.class); + assertThat(entity.getCollection()).isEqualTo("star-wars"); } static class GenericType { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/staticmetadata/StaticMetadataTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/staticmetadata/StaticMetadataTests.java new file mode 100644 index 000000000..6871ee73b --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/staticmetadata/StaticMetadataTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2020. 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. + */ + +/* + * Copyright 2020 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.mongodb.core.staticmetadata; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.data.mongodb.core.query.Criteria.*; +import static org.springframework.data.mongodb.core.query.Query.*; + +import java.util.Arrays; +import java.util.LinkedHashSet; + +import org.bson.Document; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory; +import org.springframework.data.mongodb.core.convert.MappingMongoConverter; +import org.springframework.data.mongodb.core.convert.NoOpDbRefResolver; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.util.AddressTypeInformation; +import org.springframework.data.util.ClassTypeInformation; +import org.springframework.data.util.Person; +import org.springframework.data.util.PersonTypeInformation; + +import com.mongodb.client.MongoClients; + +/** + * @author Christoph Strobl + * @since 2020/10 + */ +public class StaticMetadataTests { + + MongoMappingContext mappingContext; + MappingMongoConverter mongoConverter; + MongoTemplate template; + + Person luke; + + @BeforeAll + static void beforeAll() { + ClassTypeInformation.warmCache(PersonTypeInformation.instance(), AddressTypeInformation.instance()); + } + + @BeforeEach + void beforeEach() { + + mappingContext = new MongoMappingContext(); + mappingContext.setInitialEntitySet(new LinkedHashSet<>( + Arrays.asList(org.springframework.data.util.Person.class, org.springframework.data.util.Address.class))); + mappingContext.initialize(); + + mongoConverter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, mappingContext); + mongoConverter.afterPropertiesSet(); + + template = new MongoTemplate(new SimpleMongoClientDatabaseFactory(MongoClients.create(), "sem"), mongoConverter); + + luke = new Person("luke", "skywalker"); + luke.setAddress(new org.springframework.data.util.Address("Mos Eisley", "WB154")); + luke.setAge(22); + luke = luke.withId(9876); + luke.setNicknames(Arrays.asList("jedi", "wormie")); + } + + @Test + void readWrite() { + + template.save(luke); + + Document savedDocument = template.execute("star-wars", + collection -> collection.find(new Document("_id", luke.getId())).first()); + System.out.println("savedDocument.toJson(): " + savedDocument.toJson()); + + Person savedEntity = template.findOne(query(where("id").is(luke.getId())), Person.class); + System.out.println("savedEntity: " + savedEntity); + + assertThat(savedEntity).isEqualTo(luke); + } + +}