diff --git a/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java b/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java index 29c7da8b9..fd3d65244 100644 --- a/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java +++ b/src/main/java/org/springframework/data/mapping/IdentifierAccessor.java @@ -21,13 +21,27 @@ import java.util.Optional; * Interface for a component allowing the access of identifier values. * * @author Oliver Gierke + * @see TargetAwareIdentifierAccessor */ public interface IdentifierAccessor { /** * Returns the value of the identifier. * - * @return + * @return the identifier of the underlying instance. */ Optional getIdentifier(); + + /** + * Returns the identifier of the underlying instance. Implementations are strongly recommended to extends either + * {@link TargetAwareIdentifierAccessor} or override this method to add more context to the exception being thrown in + * case of the absence of an identifier. + * + * @return the identifier of the underlying instance + * @throws IllegalStateException in case no identifier could be retrieved. + * @since 2.0 + */ + default Object getRequiredIdentifier() { + return getIdentifier().orElseThrow(() -> new IllegalStateException(String.format("Could not obtain identifier!"))); + } } diff --git a/src/main/java/org/springframework/data/mapping/TargetAwareIdentifierAccessor.java b/src/main/java/org/springframework/data/mapping/TargetAwareIdentifierAccessor.java new file mode 100644 index 000000000..bddd86e9f --- /dev/null +++ b/src/main/java/org/springframework/data/mapping/TargetAwareIdentifierAccessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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 lombok.RequiredArgsConstructor; + +import java.util.function.Supplier; + +/** + * {@link IdentifierAccessor} that is aware of the target bean to obtain the identifier from so that it can generate a + * more meaningful exception in case of an absent identifier and a call to {@link #getRequiredIdentifier()}. + * + * @author Oliver Gierke + * @since 2.0 + * @soundtrack Anika Nilles - Greenfield (Pikalar) + */ +@RequiredArgsConstructor +public abstract class TargetAwareIdentifierAccessor implements IdentifierAccessor { + + private final Supplier target; + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.IdentifierAccessor#getRequiredIdentifier() + */ + @Override + public Object getRequiredIdentifier() { + return getIdentifier().orElseThrow( + () -> new IllegalStateException(String.format("Could not obtain identifier from %s!", target.get()))); + } +} diff --git a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java index 5b1e71a8a..e7cc4bc45 100644 --- a/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java +++ b/src/main/java/org/springframework/data/mapping/model/BasicPersistentEntity.java @@ -46,6 +46,7 @@ import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.SimpleAssociationHandler; import org.springframework.data.mapping.SimplePropertyHandler; +import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; @@ -438,7 +439,7 @@ public class BasicPersistentEntity> implement Assert.isTrue(getType().isInstance(bean), () -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName())); - return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : NullReturningIdentifierAccessor.INSTANCE; + return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean); } /** @@ -447,9 +448,11 @@ public class BasicPersistentEntity> implement * * @author Oliver Gierke */ - private static enum NullReturningIdentifierAccessor implements IdentifierAccessor { + private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor { - INSTANCE; + public AbsentIdentifierAccessor(Object target) { + super(() -> target); + } /* * (non-Javadoc) diff --git a/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java b/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java index 949ddddf9..862f7fec7 100644 --- a/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java +++ b/src/main/java/org/springframework/data/mapping/model/IdPropertyIdentifierAccessor.java @@ -21,6 +21,7 @@ import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.util.Assert; /** @@ -30,10 +31,10 @@ import org.springframework.util.Assert; * @author Oliver Gierke * @since 1.10 */ -public class IdPropertyIdentifierAccessor implements IdentifierAccessor { +public class IdPropertyIdentifierAccessor extends TargetAwareIdentifierAccessor { private final PersistentPropertyAccessor accessor; - private final Optional> idProperty; + private final PersistentProperty idProperty; /** * Creates a new {@link IdPropertyIdentifierAccessor} for the given {@link PersistentEntity} and @@ -42,13 +43,15 @@ public class IdPropertyIdentifierAccessor implements IdentifierAccessor { * @param entity must not be {@literal null}. * @param target must not be {@literal null}. */ - public IdPropertyIdentifierAccessor(PersistentEntity entity, Object target) { - Assert.notNull(entity, "PersistentEntity must not be 'null'"); - Assert.isTrue(entity.hasIdProperty(), "PersistentEntity does not have an identifier property!"); + super(() -> target); - this.idProperty = entity.getIdProperty(); + Assert.notNull(entity, "PersistentEntity must not be null!"); + Assert.isTrue(entity.hasIdProperty(), "PersistentEntity must have an identifier property!"); + Assert.notNull(target, "Target bean must not be null!"); + + this.idProperty = entity.getRequiredIdProperty(); this.accessor = entity.getPropertyAccessor(target); } @@ -57,6 +60,6 @@ public class IdPropertyIdentifierAccessor implements IdentifierAccessor { * @see org.springframework.data.keyvalue.core.IdentifierAccessor#getIdentifier() */ public Optional getIdentifier() { - return idProperty.flatMap(accessor::getProperty); + return accessor.getProperty(idProperty); } } diff --git a/src/test/java/org/springframework/data/mapping/TargetAwareIdentifierAccessorUnitTests.java b/src/test/java/org/springframework/data/mapping/TargetAwareIdentifierAccessorUnitTests.java new file mode 100644 index 000000000..0d95d1c3d --- /dev/null +++ b/src/test/java/org/springframework/data/mapping/TargetAwareIdentifierAccessorUnitTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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 static org.assertj.core.api.Assertions.*; + +import java.util.Optional; + +import org.junit.Test; + +/** + * Unit tests for {@link TargetAwareIdentifierAccessor}. + * + * @author Oliver Gierke + * @soundtrack Anika Nilles - Greenfield (Pikalar) + */ +public class TargetAwareIdentifierAccessorUnitTests { + + @Test // DATACMNS-1015 + public void throwsExceptionContainingInformationAboutTargetIfIdentifierAbsent() { + + Object sample = new Object(); + + IdentifierAccessor accessor = new TargetAwareIdentifierAccessor(() -> sample) { + + @Override + public Optional getIdentifier() { + return Optional.empty(); + } + }; + + assertThatExceptionOfType(IllegalStateException.class)// + .isThrownBy(() -> accessor.getRequiredIdentifier())// + .withMessageContaining(sample.toString()); + } +}