Skip to content

Commit e612cd0

Browse files
committed
Fix potential NullPointerException in ProjectionSerializer.
Fixes GH-1947.
1 parent 3be8146 commit e612cd0

File tree

4 files changed

+25
-8
lines changed

4 files changed

+25
-8
lines changed

spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMappings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.rest.core.mapping;
1717

1818
import org.springframework.data.util.Streamable;
19+
import org.springframework.lang.Nullable;
1920

2021
/**
2122
* @author Oliver Gierke
@@ -28,6 +29,7 @@ public interface ResourceMappings extends Streamable<ResourceMetadata> {
2829
* @param type must not be {@literal null}.
2930
* @return the {@link ResourceMetadata} if available or {@literal null} otherwise.
3031
*/
32+
@Nullable
3133
ResourceMetadata getMetadataFor(Class<?> type);
3234

3335
/**

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2Module.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,11 @@
3434
import org.springframework.data.mapping.PersistentProperty;
3535
import org.springframework.data.mapping.context.PersistentEntities;
3636
import org.springframework.data.projection.TargetAware;
37-
import org.springframework.data.repository.support.Repositories;
3837
import org.springframework.data.repository.support.RepositoryInvoker;
3938
import org.springframework.data.repository.support.RepositoryInvokerFactory;
4039
import org.springframework.data.rest.core.UriToEntityConverter;
41-
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
42-
import org.springframework.data.rest.core.mapping.ResourceMappings;
4340
import org.springframework.data.rest.core.mapping.ResourceMetadata;
4441
import org.springframework.data.rest.core.support.EntityLookup;
45-
import org.springframework.data.rest.core.support.SelfLinkProvider;
4642
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
4743
import org.springframework.data.rest.webmvc.PersistentEntityResource;
4844
import org.springframework.data.rest.webmvc.mapping.Associations;
@@ -649,7 +645,7 @@ static class ProjectionSerializer extends StdSerializer<TargetAware> {
649645
* @param invoker must not be {@literal null}.
650646
* @param unwrapping
651647
*/
652-
private ProjectionSerializer(LinkCollector collector, Associations mappings,
648+
ProjectionSerializer(LinkCollector collector, Associations mappings,
653649
RepresentationModelProcessorInvoker invoker, boolean unwrapping) {
654650

655651
super(TargetAware.class);
@@ -694,11 +690,11 @@ public JsonSerializer<TargetAware> unwrappingSerializer(NameTransformer unwrappe
694690
* @param value must not be {@literal null}.
695691
* @return
696692
*/
697-
private ProjectionResource toModel(TargetAware value) {
693+
ProjectionResource toModel(TargetAware value) {
698694

699695
Object target = value.getTarget();
700696
ResourceMetadata metadata = associations.getMetadataFor(value.getTargetClass());
701-
Links links = metadata.isExported() ? collector.getLinksFor(target) : Links.NONE;
697+
Links links = metadata != null && metadata.isExported() ? collector.getLinksFor(target) : Links.NONE;
702698

703699
EntityModel<TargetAware> resource = invoker.invokeProcessorsFor(EntityModel.of(value, links));
704700

@@ -708,7 +704,6 @@ private ProjectionResource toModel(TargetAware value) {
708704

709705
static class ProjectionResource extends EntityModel<ProjectionResourceContent> {
710706

711-
@SuppressWarnings("deprecation")
712707
ProjectionResource(TargetAware projection, Iterable<Link> links) {
713708
super(new ProjectionResourceContent(projection, projection.getClass().getInterfaces()[0]), links);
714709
}

spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/Associations.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.hateoas.TemplateVariable;
3434
import org.springframework.hateoas.TemplateVariables;
3535
import org.springframework.hateoas.UriTemplate;
36+
import org.springframework.lang.Nullable;
3637
import org.springframework.util.Assert;
3738

3839
/**
@@ -94,6 +95,7 @@ public List<Link> getLinksFor(Association<? extends PersistentProperty<?>> assoc
9495
* @param type must not be {@literal null}.
9596
* @return
9697
*/
98+
@Nullable
9799
public ResourceMetadata getMetadataFor(Class<?> type) {
98100

99101
Assert.notNull(type, "Type must not be null");

spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
3737
import org.springframework.data.mapping.PersistentProperty;
3838
import org.springframework.data.mapping.context.PersistentEntities;
39+
import org.springframework.data.projection.TargetAware;
3940
import org.springframework.data.repository.support.RepositoryInvoker;
4041
import org.springframework.data.repository.support.RepositoryInvokerFactory;
4142
import org.springframework.data.rest.core.UriToEntityConverter;
@@ -49,8 +50,10 @@
4950
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer;
5051
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer;
5152
import org.springframework.data.rest.webmvc.mapping.Associations;
53+
import org.springframework.data.rest.webmvc.mapping.DefaultLinkCollector;
5254
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
5355
import org.springframework.hateoas.EntityModel;
56+
import org.springframework.hateoas.RepresentationModel;
5457
import org.springframework.hateoas.UriTemplate;
5558
import org.springframework.hateoas.server.EntityLinks;
5659
import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker;
@@ -260,6 +263,19 @@ void doesNotWrapValuesWithoutUnwrappingSerializer() {
260263
assertThatNoException().isThrownBy(() -> mapper.writeValueAsString(model));
261264
}
262265

266+
@Test // GH-1947
267+
void projectionsHandleMissingMetadata() {
268+
269+
var collector = new DefaultLinkCollector(persistentEntities, selfLinks, associations);
270+
var invoker = mock(RepresentationModelProcessorInvoker.class);
271+
when(invoker.invokeProcessorsFor(any(RepresentationModel.class)))
272+
.then(invocation -> invocation.getArgument(0));
273+
274+
var serializer = new PersistentEntityJackson2Module.ProjectionSerializer(collector, associations, invoker, false);
275+
276+
assertThatNoException().isThrownBy(() -> serializer.toModel(mock(SampleProjection.class)));
277+
}
278+
263279
/**
264280
* @author Oliver Gierke
265281
*/
@@ -387,4 +403,6 @@ public void serialize(CustomType value, JsonGenerator gen, SerializerProvider pr
387403
gen.writeEndObject();
388404
}
389405
}
406+
407+
interface SampleProjection extends TargetAware {}
390408
}

0 commit comments

Comments
 (0)