Skip to content

Commit fc2135d

Browse files
committed
DATACMNS-1333 - Unified is-new-detection in PersistentEntity.isNew(…).
PersistentEntity now exposes an ….isNew(…) method that exposes the same detection algorithm previously exposed through MappingContextIsNewStrategyFactory (Persistable in favor of the version property in favor of an identifier lookup). MappingContextIsNewStrategyFactory has been refactored to return an ad-hoc strategy to delegate to the newly introduced method. The core message to implementing modules is that they should now prefer PersistentEntityInformation within their RepositoryFactorySupport implementation and move all customizations made in the store-specific EntityInformation implementation in PersistentEntity.
1 parent 6f7b8ce commit fc2135d

15 files changed

+383
-84
lines changed

src/main/java/org/springframework/data/auditing/IsNewAwareAuditingHandler.java

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@
2020
import org.springframework.data.mapping.PersistentEntity;
2121
import org.springframework.data.mapping.PersistentProperty;
2222
import org.springframework.data.mapping.context.MappingContext;
23-
import org.springframework.data.mapping.context.MappingContextIsNewStrategyFactory;
2423
import org.springframework.data.mapping.context.PersistentEntities;
2524
import org.springframework.data.support.IsNewStrategy;
26-
import org.springframework.data.support.IsNewStrategyFactory;
2725
import org.springframework.util.Assert;
2826

2927
/**
30-
* {@link AuditingHandler} extension that uses an {@link IsNewStrategyFactory} to expose a generic
28+
* {@link AuditingHandler} extension that uses {@link PersistentEntity#isNew(Object)} to expose a generic
3129
* {@link #markAudited(Optional)} method that will route calls to {@link #markCreated(Optional)} or
3230
* {@link #markModified(Optional)} based on the {@link IsNewStrategy} determined from the factory.
3331
*
@@ -37,7 +35,7 @@
3735
*/
3836
public class IsNewAwareAuditingHandler extends AuditingHandler {
3937

40-
private final IsNewStrategyFactory isNewStrategyFactory;
38+
private final PersistentEntities entities;
4139

4240
/**
4341
* Creates a new {@link IsNewAwareAuditingHandler} for the given {@link MappingContext}.
@@ -62,13 +60,12 @@ public IsNewAwareAuditingHandler(PersistentEntities entities) {
6260

6361
super(entities);
6462

65-
this.isNewStrategyFactory = new MappingContextIsNewStrategyFactory(entities);
63+
this.entities = entities;
6664
}
6765

6866
/**
69-
* Marks the given object created or modified based on the {@link IsNewStrategy} returned by the
70-
* {@link IsNewStrategyFactory} configured. Will rout the calls to {@link #markCreated(Optional)} and
71-
* {@link #markModified(Optional)} accordingly.
67+
* Marks the given object created or modified based on {@link PersistentEntity#isNew(Object)}. Will route the calls to
68+
* {@link #markCreated(Optional)} and {@link #markModified(Optional)} accordingly.
7269
*
7370
* @param object
7471
*/
@@ -80,9 +77,10 @@ public void markAudited(Object object) {
8077
return;
8178
}
8279

83-
IsNewStrategy strategy = isNewStrategyFactory.getIsNewStrategy(object.getClass());
80+
PersistentEntity<?, ? extends PersistentProperty<?>> entity = entities
81+
.getRequiredPersistentEntity(object.getClass());
8482

85-
if (strategy.isNew(object)) {
83+
if (entity.isNew(object)) {
8684
markCreated(object);
8785
} else {
8886
markModified(object);

src/main/java/org/springframework/data/mapping/PersistentEntity.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,14 @@ default <A extends Annotation> A getRequiredAnnotation(Class<A> annotationType)
295295
* @since 1.10
296296
*/
297297
IdentifierAccessor getIdentifierAccessor(Object bean);
298+
299+
/**
300+
* Returns whether the given bean is considered new according to the static metadata.
301+
*
302+
* @param bean must not be {@literal null}.
303+
* @throws IllegalArgumentException in case the given bean is not an instance of the typ represented by the
304+
* {@link PersistentEntity}.
305+
* @return whether the given bean is considered a new instance.
306+
*/
307+
boolean isNew(Object bean);
298308
}

src/main/java/org/springframework/data/mapping/context/MappingContextIsNewStrategyFactory.java

Lines changed: 8 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@
1515
*/
1616
package org.springframework.data.mapping.context;
1717

18-
import lombok.NonNull;
19-
import lombok.RequiredArgsConstructor;
20-
21-
import java.util.function.Function;
22-
2318
import org.springframework.data.mapping.PersistentEntity;
24-
import org.springframework.data.mapping.PersistentProperty;
2519
import org.springframework.data.support.IsNewStrategy;
2620
import org.springframework.data.support.IsNewStrategyFactory;
2721
import org.springframework.data.support.IsNewStrategyFactorySupport;
@@ -36,10 +30,13 @@
3630
*
3731
* @author Oliver Gierke
3832
* @author Mark Paluch
33+
* @deprecated as of 2.1 in favor of looking up the {@link PersistentEntity} and calling
34+
* {@link PersistentEntity#isNew(Object)} on it
3935
*/
36+
@Deprecated
4037
public class MappingContextIsNewStrategyFactory extends IsNewStrategyFactorySupport {
4138

42-
private final PersistentEntities context;
39+
private final PersistentEntities entities;
4340

4441
/**
4542
* Creates a new {@link MappingContextIsNewStrategyFactory} using the given {@link MappingContext}.
@@ -61,7 +58,8 @@ public MappingContextIsNewStrategyFactory(MappingContext<? extends PersistentEnt
6158
public MappingContextIsNewStrategyFactory(PersistentEntities entities) {
6259

6360
Assert.notNull(entities, "PersistentEntities must not be null!");
64-
this.context = entities;
61+
62+
this.entities = entities;
6563
}
6664

6765
/*
@@ -72,50 +70,8 @@ public MappingContextIsNewStrategyFactory(PersistentEntities entities) {
7270
@Override
7371
protected IsNewStrategy doGetIsNewStrategy(Class<?> type) {
7472

75-
PersistentEntity<?, ? extends PersistentProperty<?>> entity = context.getRequiredPersistentEntity(type);
76-
77-
if (entity.hasVersionProperty()) {
78-
79-
return PersistentPropertyInspectingIsNewStrategy.of(entity.getRequiredVersionProperty(),
80-
MappingContextIsNewStrategyFactory::propertyIsNullOrZeroNumber);
81-
82-
}
83-
84-
if (entity.hasIdProperty()) {
85-
86-
return PersistentPropertyInspectingIsNewStrategy.of(entity.getRequiredIdProperty(),
87-
MappingContextIsNewStrategyFactory::propertyIsNullOrZeroNumber);
88-
}
89-
90-
return null;
91-
}
92-
93-
/**
94-
* {@link IsNewStrategy} implementation that will inspect a given {@link PersistentProperty} and call
95-
* {@link #isNew(Object)} with the value retrieved by reflection.
96-
*
97-
* @author Oliver Gierke
98-
*/
99-
@RequiredArgsConstructor(staticName = "of")
100-
static class PersistentPropertyInspectingIsNewStrategy implements IsNewStrategy {
101-
102-
private final @NonNull PersistentProperty<?> property;
103-
private final @NonNull Function<Object, Boolean> isNew;
104-
105-
/*
106-
* (non-Javadoc)
107-
* @see org.springframework.data.support.IsNewStrategy#isNew(java.util.Optional)
108-
*/
109-
@Override
110-
public boolean isNew(Object entity) {
111-
112-
Assert.notNull(entity, "Entity must not be null!");
113-
114-
return isNew.apply(property.getOwner().getPropertyAccessor(entity).getProperty(property));
115-
}
116-
}
73+
PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(type);
11774

118-
private static boolean propertyIsNullOrZeroNumber(Object value) {
119-
return value == null || value instanceof Number && ((Number) value).longValue() == 0;
75+
return bean -> entity.isNew(bean);
12076
}
12177
}

src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949
import org.springframework.data.mapping.SimplePropertyHandler;
5050
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
5151
import org.springframework.data.spel.EvaluationContextProvider;
52+
import org.springframework.data.support.IsNewStrategy;
53+
import org.springframework.data.support.PersistableIsNewStrategy;
5254
import org.springframework.data.util.Lazy;
5355
import org.springframework.data.util.TypeInformation;
5456
import org.springframework.expression.EvaluationContext;
@@ -90,6 +92,7 @@ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implement
9092
private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
9193

9294
private final Lazy<Alias> typeAlias;
95+
private final Lazy<IsNewStrategy> isNewStrategy;
9396

9497
/**
9598
* Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}.
@@ -124,6 +127,10 @@ public BasicPersistentEntity(TypeInformation<T> information, @Nullable Comparato
124127
this.propertyAnnotationCache = CollectionUtils.toMultiValueMap(new ConcurrentReferenceHashMap<>());
125128
this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE;
126129
this.typeAlias = Lazy.of(() -> getAliasFromAnnotation(getType()));
130+
this.isNewStrategy = Lazy.of(() -> Persistable.class.isAssignableFrom(information.getType()) //
131+
? PersistableIsNewStrategy.INSTANCE
132+
: PersistentEntityIsNewStrategy.of(this));
133+
127134
}
128135

129136
/*
@@ -456,9 +463,7 @@ public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFacto
456463
@Override
457464
public PersistentPropertyAccessor getPropertyAccessor(Object bean) {
458465

459-
Assert.notNull(bean, "Target bean must not be null!");
460-
Assert.isTrue(getType().isInstance(bean),
461-
() -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
466+
verifyBeanType(bean);
462467

463468
return propertyAccessorFactory.getPropertyAccessor(this, bean);
464469
}
@@ -470,9 +475,7 @@ public PersistentPropertyAccessor getPropertyAccessor(Object bean) {
470475
@Override
471476
public IdentifierAccessor getIdentifierAccessor(Object bean) {
472477

473-
Assert.notNull(bean, "Target bean must not be null!");
474-
Assert.isTrue(getType().isInstance(bean),
475-
() -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
478+
verifyBeanType(bean);
476479

477480
if (Persistable.class.isAssignableFrom(getType())) {
478481
return new PersistableIdentifierAccessor((Persistable<?>) bean);
@@ -481,6 +484,18 @@ public IdentifierAccessor getIdentifierAccessor(Object bean) {
481484
return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean);
482485
}
483486

487+
/*
488+
* (non-Javadoc)
489+
* @see org.springframework.data.mapping.PersistentEntity#isNew(java.lang.Object)
490+
*/
491+
@Override
492+
public boolean isNew(Object bean) {
493+
494+
verifyBeanType(bean);
495+
496+
return isNewStrategy.get().isNew(bean);
497+
}
498+
484499
/*
485500
* (non-Javadoc)
486501
* @see java.lang.Iterable#iterator()
@@ -494,6 +509,18 @@ protected EvaluationContext getEvaluationContext(Object rootObject) {
494509
return evaluationContextProvider.getEvaluationContext(rootObject);
495510
}
496511

512+
/**
513+
* Verifies the given bean type to no be {@literal null} and of the type of the current {@link PersistentEntity}.
514+
*
515+
* @param bean must not be {@literal null}.
516+
*/
517+
private final void verifyBeanType(Object bean) {
518+
519+
Assert.notNull(bean, "Target bean must not be null!");
520+
Assert.isInstanceOf(getType(), bean,
521+
() -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
522+
}
523+
497524
/**
498525
* Calculates the {@link Alias} to be used for the given type.
499526
*
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mapping.model;
17+
18+
import java.util.function.Function;
19+
20+
import org.springframework.data.mapping.PersistentEntity;
21+
import org.springframework.data.support.IsNewStrategy;
22+
import org.springframework.lang.Nullable;
23+
import org.springframework.util.Assert;
24+
import org.springframework.util.ClassUtils;
25+
26+
/**
27+
* An {@link IsNewStrategy} to use a {@link PersistentEntity}'s version property followed by it
28+
*
29+
* @author Oliver Gierke
30+
* @soundtrack Scary Pockets - Crash Into Me (Dave Matthews Band Cover feat. Julia Nunes) -
31+
* https://www.youtube.com/watch?v=syGlBNVGEqU
32+
*/
33+
class PersistentEntityIsNewStrategy implements IsNewStrategy {
34+
35+
private final Function<Object, Object> valueLookup;
36+
private final @Nullable Class<?> valueType;
37+
38+
/**
39+
* Creates a new {@link PersistentEntityIsNewStrategy} for the given entity.
40+
*
41+
* @param entity must not be {@literal null}.
42+
*/
43+
private PersistentEntityIsNewStrategy(PersistentEntity<?, ?> entity, boolean idOnly) {
44+
45+
Assert.notNull(entity, "PersistentEntity must not be null!");
46+
47+
this.valueLookup = entity.hasVersionProperty() && !idOnly //
48+
? source -> entity.getPropertyAccessor(source).getProperty(entity.getRequiredVersionProperty())
49+
: source -> entity.getIdentifierAccessor(source).getIdentifier();
50+
51+
this.valueType = entity.hasVersionProperty() && !idOnly //
52+
? entity.getRequiredVersionProperty().getType() //
53+
: entity.hasIdProperty() ? entity.getRequiredIdProperty().getType() : null;
54+
55+
Class<?> type = valueType;
56+
57+
if (type != null && type.isPrimitive()) {
58+
59+
if (!ClassUtils.isAssignable(Number.class, type)) {
60+
61+
throw new IllegalArgumentException(String
62+
.format("Only numeric primitives are supported as identifier / version field types! Got: %s.", valueType));
63+
}
64+
}
65+
}
66+
67+
/**
68+
* Creates a new {@link PersistentEntityIsNewStrategy} to only consider the identifier of the given entity.
69+
*
70+
* @param entity must not be {@literal null}.
71+
* @return
72+
*/
73+
public static PersistentEntityIsNewStrategy forIdOnly(PersistentEntity<?, ?> entity) {
74+
return new PersistentEntityIsNewStrategy(entity, true);
75+
}
76+
77+
/**
78+
* Creates a new {@link PersistentEntityIsNewStrategy} to consider version properties before falling back to the
79+
* identifier.
80+
*
81+
* @param entity must not be {@literal null}.
82+
* @return
83+
*/
84+
public static PersistentEntityIsNewStrategy of(PersistentEntity<?, ?> entity) {
85+
return new PersistentEntityIsNewStrategy(entity, false);
86+
}
87+
88+
/*
89+
* (non-Javadoc)
90+
* @see org.springframework.data.support.IsNewStrategy#isNew(java.lang.Object)
91+
*/
92+
@Override
93+
public boolean isNew(Object entity) {
94+
95+
Object value = valueLookup.apply(entity);
96+
97+
if (value == null) {
98+
return true;
99+
}
100+
101+
if (valueType != null && !valueType.isPrimitive()) {
102+
return value == null;
103+
}
104+
105+
if (Number.class.isInstance(value)) {
106+
return ((Number) value).longValue() == 0;
107+
}
108+
109+
throw new IllegalArgumentException(
110+
String.format("Could not determine whether %s is new! Unsupported identifier or version property!", entity));
111+
}
112+
}

src/main/java/org/springframework/data/repository/core/EntityInformation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*
2424
* @author Oliver Gierke
2525
* @author Mark Paluch
26+
* @see org.springframework.data.repository.core.support.PersistentEntityInformation
2627
*/
2728
public interface EntityInformation<T, ID> extends EntityMetadata<T> {
2829

src/main/java/org/springframework/data/repository/core/support/PersistableEntityInformation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
* {@link Persistable#isNew()} for the {@link #isNew(Object)} check.
2626
*
2727
* @author Oliver Gierke
28+
* @deprecated as of 2.1 in favor of {@link PersistentEntityInformation}.
2829
*/
30+
@Deprecated
2931
public class PersistableEntityInformation<T extends Persistable<ID>, ID> extends AbstractEntityInformation<T, ID> {
3032

3133
private Class<ID> idClass;

0 commit comments

Comments
 (0)