Fix potential NullPointerException in ProjectionSerializer.

Fixes GH-1947.
This commit is contained in:
Oliver Drotbohm
2023-12-15 12:18:27 +01:00
parent 20a1218a28
commit 898b368510
4 changed files with 25 additions and 8 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.data.rest.core.mapping;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
/**
* @author Oliver Gierke
@@ -28,6 +29,7 @@ public interface ResourceMappings extends Streamable<ResourceMetadata> {
* @param type must not be {@literal null}.
* @return the {@link ResourceMetadata} if available or {@literal null} otherwise.
*/
@Nullable
ResourceMetadata getMetadataFor(Class<?> type);
/**

View File

@@ -34,15 +34,11 @@ import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.data.rest.core.support.SelfLinkProvider;
import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.mapping.Associations;
@@ -649,7 +645,7 @@ public class PersistentEntityJackson2Module extends SimpleModule {
* @param invoker must not be {@literal null}.
* @param unwrapping
*/
private ProjectionSerializer(LinkCollector collector, Associations mappings,
ProjectionSerializer(LinkCollector collector, Associations mappings,
RepresentationModelProcessorInvoker invoker, boolean unwrapping) {
super(TargetAware.class);
@@ -694,11 +690,11 @@ public class PersistentEntityJackson2Module extends SimpleModule {
* @param value must not be {@literal null}.
* @return
*/
private ProjectionResource toModel(TargetAware value) {
ProjectionResource toModel(TargetAware value) {
Object target = value.getTarget();
ResourceMetadata metadata = associations.getMetadataFor(value.getTargetClass());
Links links = metadata.isExported() ? collector.getLinksFor(target) : Links.NONE;
Links links = metadata != null && metadata.isExported() ? collector.getLinksFor(target) : Links.NONE;
EntityModel<TargetAware> resource = invoker.invokeProcessorsFor(EntityModel.of(value, links));
@@ -708,7 +704,6 @@ public class PersistentEntityJackson2Module extends SimpleModule {
static class ProjectionResource extends EntityModel<ProjectionResourceContent> {
@SuppressWarnings("deprecation")
ProjectionResource(TargetAware projection, Iterable<Link> links) {
super(new ProjectionResourceContent(projection, projection.getClass().getInterfaces()[0]), links);
}

View File

@@ -33,6 +33,7 @@ import org.springframework.hateoas.Link;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.UriTemplate;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -94,6 +95,7 @@ public class Associations {
* @param type must not be {@literal null}.
* @return
*/
@Nullable
public ResourceMetadata getMetadataFor(Class<?> type) {
Assert.notNull(type, "Type must not be null");

View File

@@ -39,6 +39,7 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.support.RepositoryInvoker;
import org.springframework.data.repository.support.RepositoryInvokerFactory;
import org.springframework.data.rest.core.UriToEntityConverter;
@@ -52,8 +53,10 @@ import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer;
import org.springframework.data.rest.webmvc.mapping.Associations;
import org.springframework.data.rest.webmvc.mapping.DefaultLinkCollector;
import org.springframework.data.rest.webmvc.support.ExcerptProjector;
import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.UriTemplate;
import org.springframework.hateoas.server.EntityLinks;
import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker;
@@ -263,6 +266,19 @@ class PersistentEntityJackson2ModuleUnitTests {
assertThatNoException().isThrownBy(() -> mapper.writeValueAsString(model));
}
@Test // GH-1947
void projectionsHandleMissingMetadata() {
var collector = new DefaultLinkCollector(persistentEntities, selfLinks, associations);
var invoker = mock(RepresentationModelProcessorInvoker.class);
when(invoker.invokeProcessorsFor(any(RepresentationModel.class)))
.then(invocation -> invocation.getArgument(0));
var serializer = new PersistentEntityJackson2Module.ProjectionSerializer(collector, associations, invoker, false);
assertThatNoException().isThrownBy(() -> serializer.toModel(mock(SampleProjection.class)));
}
/**
* @author Oliver Gierke
*/
@@ -365,4 +381,6 @@ class PersistentEntityJackson2ModuleUnitTests {
gen.writeEndObject();
}
}
interface SampleProjection extends TargetAware {}
}