Skip to content

Commit 6b5b658

Browse files
committed
DATACMNS-1015 - Introduced IdentifierAccessor.getRequiredIdentifier().
The new method allows to look up identifiers and immediately fail in the case of absence. Introduced the method as default method but also a new base type TargetAwareIdentifierAccessor to throw an exception with more context, i.e. the actual target bean we try to look up the identifier on.
1 parent 790bfc6 commit 6b5b658

File tree

5 files changed

+124
-11
lines changed

5 files changed

+124
-11
lines changed

Diff for: src/main/java/org/springframework/data/mapping/IdentifierAccessor.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,27 @@
2121
* Interface for a component allowing the access of identifier values.
2222
*
2323
* @author Oliver Gierke
24+
* @see TargetAwareIdentifierAccessor
2425
*/
2526
public interface IdentifierAccessor {
2627

2728
/**
2829
* Returns the value of the identifier.
2930
*
30-
* @return
31+
* @return the identifier of the underlying instance.
3132
*/
3233
Optional<Object> getIdentifier();
34+
35+
/**
36+
* Returns the identifier of the underlying instance. Implementations are strongly recommended to extends either
37+
* {@link TargetAwareIdentifierAccessor} or override this method to add more context to the exception being thrown in
38+
* case of the absence of an identifier.
39+
*
40+
* @return the identifier of the underlying instance
41+
* @throws IllegalStateException in case no identifier could be retrieved.
42+
* @since 2.0
43+
*/
44+
default Object getRequiredIdentifier() {
45+
return getIdentifier().orElseThrow(() -> new IllegalStateException(String.format("Could not obtain identifier!")));
46+
}
3347
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2017 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;
17+
18+
import lombok.RequiredArgsConstructor;
19+
20+
import java.util.function.Supplier;
21+
22+
/**
23+
* {@link IdentifierAccessor} that is aware of the target bean to obtain the identifier from so that it can generate a
24+
* more meaningful exception in case of an absent identifier and a call to {@link #getRequiredIdentifier()}.
25+
*
26+
* @author Oliver Gierke
27+
* @since 2.0
28+
* @soundtrack Anika Nilles - Greenfield (Pikalar)
29+
*/
30+
@RequiredArgsConstructor
31+
public abstract class TargetAwareIdentifierAccessor implements IdentifierAccessor {
32+
33+
private final Supplier<Object> target;
34+
35+
/*
36+
* (non-Javadoc)
37+
* @see org.springframework.data.mapping.IdentifierAccessor#getRequiredIdentifier()
38+
*/
39+
@Override
40+
public Object getRequiredIdentifier() {
41+
return getIdentifier().orElseThrow(
42+
() -> new IllegalStateException(String.format("Could not obtain identifier from %s!", target.get())));
43+
}
44+
}

Diff for: src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.data.mapping.PropertyHandler;
4747
import org.springframework.data.mapping.SimpleAssociationHandler;
4848
import org.springframework.data.mapping.SimplePropertyHandler;
49+
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
4950
import org.springframework.data.util.Lazy;
5051
import org.springframework.data.util.TypeInformation;
5152
import org.springframework.util.Assert;
@@ -438,7 +439,7 @@ public IdentifierAccessor getIdentifierAccessor(Object bean) {
438439
Assert.isTrue(getType().isInstance(bean),
439440
() -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName()));
440441

441-
return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : NullReturningIdentifierAccessor.INSTANCE;
442+
return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean);
442443
}
443444

444445
/**
@@ -447,9 +448,11 @@ public IdentifierAccessor getIdentifierAccessor(Object bean) {
447448
*
448449
* @author Oliver Gierke
449450
*/
450-
private static enum NullReturningIdentifierAccessor implements IdentifierAccessor {
451+
private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor {
451452

452-
INSTANCE;
453+
public AbsentIdentifierAccessor(Object target) {
454+
super(() -> target);
455+
}
453456

454457
/*
455458
* (non-Javadoc)

Diff for: src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java

+10-7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.data.mapping.PersistentEntity;
2222
import org.springframework.data.mapping.PersistentProperty;
2323
import org.springframework.data.mapping.PersistentPropertyAccessor;
24+
import org.springframework.data.mapping.TargetAwareIdentifierAccessor;
2425
import org.springframework.util.Assert;
2526

2627
/**
@@ -30,10 +31,10 @@
3031
* @author Oliver Gierke
3132
* @since 1.10
3233
*/
33-
public class IdPropertyIdentifierAccessor implements IdentifierAccessor {
34+
public class IdPropertyIdentifierAccessor extends TargetAwareIdentifierAccessor {
3435

3536
private final PersistentPropertyAccessor accessor;
36-
private final Optional<? extends PersistentProperty<?>> idProperty;
37+
private final PersistentProperty<?> idProperty;
3738

3839
/**
3940
* Creates a new {@link IdPropertyIdentifierAccessor} for the given {@link PersistentEntity} and
@@ -42,13 +43,15 @@ public class IdPropertyIdentifierAccessor implements IdentifierAccessor {
4243
* @param entity must not be {@literal null}.
4344
* @param target must not be {@literal null}.
4445
*/
45-
4646
public IdPropertyIdentifierAccessor(PersistentEntity<?, ?> entity, Object target) {
4747

48-
Assert.notNull(entity, "PersistentEntity must not be 'null'");
49-
Assert.isTrue(entity.hasIdProperty(), "PersistentEntity does not have an identifier property!");
48+
super(() -> target);
49+
50+
Assert.notNull(entity, "PersistentEntity must not be null!");
51+
Assert.isTrue(entity.hasIdProperty(), "PersistentEntity must have an identifier property!");
52+
Assert.notNull(target, "Target bean must not be null!");
5053

51-
this.idProperty = entity.getIdProperty();
54+
this.idProperty = entity.getRequiredIdProperty();
5255
this.accessor = entity.getPropertyAccessor(target);
5356
}
5457

@@ -57,6 +60,6 @@ public IdPropertyIdentifierAccessor(PersistentEntity<?, ?> entity, Object target
5760
* @see org.springframework.data.keyvalue.core.IdentifierAccessor#getIdentifier()
5861
*/
5962
public Optional<Object> getIdentifier() {
60-
return idProperty.flatMap(accessor::getProperty);
63+
return accessor.getProperty(idProperty);
6164
}
6265
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright 2017 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;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
20+
import java.util.Optional;
21+
22+
import org.junit.Test;
23+
24+
/**
25+
* Unit tests for {@link TargetAwareIdentifierAccessor}.
26+
*
27+
* @author Oliver Gierke
28+
* @soundtrack Anika Nilles - Greenfield (Pikalar)
29+
*/
30+
public class TargetAwareIdentifierAccessorUnitTests {
31+
32+
@Test // DATACMNS-1015
33+
public void throwsExceptionContainingInformationAboutTargetIfIdentifierAbsent() {
34+
35+
Object sample = new Object();
36+
37+
IdentifierAccessor accessor = new TargetAwareIdentifierAccessor(() -> sample) {
38+
39+
@Override
40+
public Optional<Object> getIdentifier() {
41+
return Optional.empty();
42+
}
43+
};
44+
45+
assertThatExceptionOfType(IllegalStateException.class)//
46+
.isThrownBy(() -> accessor.getRequiredIdentifier())//
47+
.withMessageContaining(sample.toString());
48+
}
49+
}

0 commit comments

Comments
 (0)