Skip to content

Throw UnsupportedOperationException when a projected value cannot be returned #2291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.5.0-GH-2290-SNAPSHOT</version>

<name>Spring Data Core</name>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ private Pair<DefaultPersistentPropertyPath<P>, E> getPair(DefaultPersistentPrope
return null;
}

TypeInformation<?> type = property.getTypeInformation().getRequiredActualType();
return Pair.of(path.append(property), iterator.hasNext() ? context.getRequiredPersistentEntity(type) : entity);
return Pair.of(path.append(property), iterator.hasNext() ? context.getRequiredPersistentEntity(property) : entity);
}

private <T> Collection<PersistentPropertyPath<P>> from(TypeInformation<T> type, Predicate<? super P> filter,
Expand All @@ -236,6 +235,12 @@ private <T> Collection<PersistentPropertyPath<P>> from(TypeInformation<T> type,
}

E entity = context.getRequiredPersistentEntity(actualType);
return from(entity, filter, traversalGuard, basePath);
}

private Collection<PersistentPropertyPath<P>> from(E entity, Predicate<? super P> filter, Predicate<P> traversalGuard,
DefaultPersistentPropertyPath<P> basePath) {

Set<PersistentPropertyPath<P>> properties = new HashSet<>();

PropertyHandler<P> propertyTester = persistentProperty -> {
Expand All @@ -254,7 +259,7 @@ private <T> Collection<PersistentPropertyPath<P>> from(TypeInformation<T> type,
}

if (traversalGuard.and(IS_ENTITY).test(persistentProperty)) {
properties.addAll(from(typeInformation, filter, traversalGuard, currentPath));
properties.addAll(from(context.getPersistentEntity(persistentProperty), filter, traversalGuard, currentPath));
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.util.ClassTypeInformation;
Expand All @@ -45,6 +44,7 @@
*
* @author Oliver Gierke
* @author Mark Paluch
* @author Christoph Strobl
* @since 1.10
*/
class ProjectingMethodInterceptor implements MethodInterceptor {
Expand Down Expand Up @@ -98,14 +98,22 @@ protected Object potentiallyConvertResult(TypeInformation<?> type, @Nullable Obj
return null;
}

if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(type.getType())) {
Class<?> targetType = type.getType();

if (type.isCollectionLike() && !ClassUtils.isPrimitiveArray(targetType)) {
return projectCollectionElements(asCollection(result), type);
} else if (type.isMap()) {
return projectMapValues((Map<?, ?>) result, type);
} else if (conversionRequiredAndPossible(result, type.getType())) {
return conversionService.convert(result, type.getType());
} else if (ClassUtils.isAssignable(targetType, result.getClass())) {
return result;
} else if (conversionService.canConvert(result.getClass(), targetType)) {
return conversionService.convert(result, targetType);
} else if (targetType.isInterface()) {
return getProjection(result, targetType);
} else {
return getProjection(result, type.getType());
throw new UnsupportedOperationException(
String.format("Cannot project %s to %s. Target type is not an interface and no matching Converter found!",
ClassUtils.getDescriptiveType(result), ClassUtils.getQualifiedName(targetType)));
}
}

Expand Down Expand Up @@ -155,28 +163,11 @@ private Map<Object, Object> projectMapValues(Map<?, ?> sources, TypeInformation<
}

@Nullable
private Object getProjection(Object result, Class<?> returnType) {
private Object getProjection(@Nullable Object result, Class<?> returnType) {
return result == null || ClassUtils.isAssignable(returnType, result.getClass()) ? result
: factory.createProjection(returnType, result);
}

/**
* Returns whether the source object needs to be converted to the given target type and whether we can convert it at
* all.
*
* @param source can be {@literal null}.
* @param targetType must not be {@literal null}.
* @return
*/
private boolean conversionRequiredAndPossible(Object source, Class<?> targetType) {

if (source == null || targetType.isInstance(source)) {
return false;
}

return conversionService.canConvert(source.getClass(), targetType);
}

/**
* Turns the given value into a {@link Collection}. Will turn an array into a collection an wrap all other values into
* a single-element collection.
Expand Down
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.math.BigInteger;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
Expand All @@ -42,6 +43,7 @@
* @author Oliver Gierke
* @author Saulo Medeiros de Araujo
* @author Mark Paluch
* @author Christoph Strobl
*/
@ExtendWith(MockitoExtension.class)
class ProjectingMethodInterceptorUnitTests {
Expand Down Expand Up @@ -95,6 +97,20 @@ void considersPrimitivesAsWrappers() throws Throwable {
verify(factory, times(0)).createProjection((Class<?>) any(), any());
}

@Test // GH-2290
void failsForNonConvertibleTypes() throws Throwable {

MethodInterceptor methodInterceptor = new ProjectingMethodInterceptor(factory, interceptor, conversionService);

when(invocation.getMethod()).thenReturn(Helper.class.getMethod("getBoolean"));
when(interceptor.invoke(invocation)).thenReturn(BigInteger.valueOf(1));

assertThatThrownBy(() -> methodInterceptor.invoke(invocation)) //
.isInstanceOf(UnsupportedOperationException.class) //
.hasMessageContaining("BigInteger") //
.hasMessageContaining("boolean");
}

@Test // DATAREST-394, DATAREST-408
@SuppressWarnings("unchecked")
void appliesProjectionToNonEmptySets() throws Throwable {
Expand Down Expand Up @@ -213,6 +229,8 @@ interface Helper {

long getPrimitive();

boolean getBoolean();

Collection<HelperProjection> getHelperCollection();

List<HelperProjection> getHelperList();
Expand Down