Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,26 @@ private Object getResourceId(Class<?> type, @Nullable Object reference) {

private @Nullable Object entityIdentifierOrNull(Object instance) {

return entities.getRequiredPersistentEntity(instance.getClass()) //
.getIdentifierAccessor(instance) //
.getIdentifier();
PersistentEntity<?, ?> entity = entities.getRequiredPersistentEntity(instance.getClass());

// First try the standard IdentifierAccessor path (works for simple @Id)
Object identifier = entity.getIdentifierAccessor(instance).getIdentifier();

if (identifier != null) {
return identifier;
}

// Fall back to reading the ID property directly via PersistentPropertyAccessor.
// This handles composite keys (@EmbeddedId / @IdClass) where the IdentifierAccessor
// may return null because the mapping layer cannot resolve the composite key through
// its standard path (e.g. when the key type contains non-primitive fields such as
// Joda-Time DateTime that are not registered in the mapping context).
PersistentProperty<?> idProperty = entity.getIdProperty();

if (idProperty == null) {
return null;
}

return entity.getPropertyAccessor(instance).getProperty(idProperty);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
Expand All @@ -30,8 +31,13 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.annotation.Id;
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
import org.springframework.data.mapping.IdentifierAccessor;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.rest.core.domain.Profile;
import org.springframework.hateoas.Link;
Expand Down Expand Up @@ -129,4 +135,68 @@ void rejectsLinkCreationForUnknownEntity() {
.withMessageContaining(Object.class.getName()) //
.withMessageContaining("Couldn't find PersistentEntity for");
}

@Test // DATAREST-846
@SuppressWarnings({ "unchecked", "rawtypes" })
void fallsBackToIdPropertyAccessorWhenIdentifierAccessorReturnsNullForCompositeKey() {

// Simulate an entity whose IdentifierAccessor returns null (as happens with
// @EmbeddedId / @IdClass in Spring Data JPA when the mapping context cannot
// resolve the composite key via its standard path), but whose ID property
// is still readable via PersistentPropertyAccessor.
CompositeKey compositeKey = new CompositeKey(1L, "part2");

IdentifierAccessor identifierAccessor = mock(IdentifierAccessor.class);
when(identifierAccessor.getIdentifier()).thenReturn(null);

PersistentProperty idProperty = mock(PersistentProperty.class);

PersistentPropertyAccessor propertyAccessor = mock(PersistentPropertyAccessor.class);
when(propertyAccessor.getProperty(idProperty)).thenReturn(compositeKey);

PersistentEntity persistentEntity = mock(PersistentEntity.class);
when(persistentEntity.getIdentifierAccessor(any())).thenReturn(identifierAccessor);
when(persistentEntity.getIdProperty()).thenReturn(idProperty);
when(persistentEntity.getPropertyAccessor(any())).thenReturn(propertyAccessor);

PersistentEntities mockEntities = mock(PersistentEntities.class);
when(mockEntities.getRequiredPersistentEntity(CompositeKeyEntity.class)).thenReturn(persistentEntity);

SelfLinkProvider providerUnderTest = new DefaultSelfLinkProvider(mockEntities, entityLinks, lookups,
conversionService);

CompositeKeyEntity entity = new CompositeKeyEntity(compositeKey);
Link link = providerUnderTest.createSelfLinkFor(entity);

assertThat(link.getHref()).endsWith(compositeKey.toString());
}

// ---------------------------------------------------------------------------
// Helper types for the composite-key test
// ---------------------------------------------------------------------------

static class CompositeKey implements Serializable {

final Long part1;
final String part2;

CompositeKey(Long part1, String part2) {
this.part1 = part1;
this.part2 = part2;
}

@Override
public String toString() {
return part1 + "_" + part2;
}
}

static class CompositeKeyEntity {

@Id CompositeKey id;

CompositeKeyEntity(CompositeKey id) {
this.id = id;
}
}
}
Loading