diff --git a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java index 77aa64102..0770e569b 100644 --- a/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java +++ b/src/main/java/org/springframework/data/repository/support/DomainClassConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2008-2014 the original author or authors. + * Copyright 2008-2015 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. @@ -25,7 +25,9 @@ import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.core.convert.converter.ConverterRegistry; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.core.EntityInformation; import org.springframework.data.repository.core.RepositoryInformation; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -42,9 +44,18 @@ public class DomainClassConverter domainType = targetType.getType(); - - RepositoryInformation info = repositories.getRepositoryInformationFor(domainType); - RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType); - - return invoker.invokeFindOne(conversionService.convert(source, info.getIdType())); + return repositories.hasRepositoryFor(targetType.getType()) ? toEntityConverter.convert(source, sourceType, + targetType) : toIdConverter.convert(source, sourceType, targetType); } - /* + /* * (non-Javadoc) - * @see org.springframework.core.convert.converter.ConditionalGenericConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) */ + @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - if (!repositories.hasRepositoryFor(targetType.getType())) { + try { + return toEntityConverter.matches(sourceType, targetType) || toIdConverter.matches(sourceType, targetType); + } catch (ConversionMatchAbbreviationException o_O) { return false; } - - if (sourceType.equals(targetType)) { - return true; - } - - return conversionService.canConvert(sourceType.getType(), - repositories.getRepositoryInformationFor(targetType.getType()).getIdType()); } /* @@ -103,7 +98,155 @@ public class DomainClassConverter getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + + if (source == null || !StringUtils.hasText(source.toString())) { + return null; + } + + if (sourceType.equals(targetType)) { + return source; + } + + Class domainType = targetType.getType(); + + RepositoryInformation info = repositories.getRepositoryInformationFor(domainType); + RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType); + + return invoker.invokeFindOne(conversionService.convert(source, info.getIdType())); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + + if (!repositories.hasRepositoryFor(targetType.getType())) { + return false; + } + + if (sourceType.equals(targetType)) { + return true; + } + + Class rawIdType = repositories.getRepositoryInformationFor(targetType.getType()).getIdType(); + + if (sourceType.equals(TypeDescriptor.valueOf(rawIdType)) + || conversionService.canConvert(sourceType.getType(), rawIdType)) { + return true; + } + + // Throw an exception to indicate it should have matched an no further resolution should be tried + throw new ConversionMatchAbbreviationException(); + } + } + + /** + * Converter to turn domain types into their identifiers or any transitively convertible type. + * + * @author Oliver Gierke + * @since 1.10 + */ + private class ToIdConverter implements ConditionalGenericConverter { + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() + */ + @Override + public Set getConvertibleTypes() { + return Collections.singleton(new ConvertiblePair(Object.class, Object.class)); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Override + public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + + if (source == null || !StringUtils.hasText(source.toString())) { + return null; + } + + if (sourceType.equals(targetType)) { + return source; + } + + Class domainType = sourceType.getType(); + + EntityInformation entityInformation = repositories.getEntityInformationFor(domainType); + + return conversionService.convert(entityInformation.getId(source), targetType.getType()); + } + + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ + @Override + public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + + if (!repositories.hasRepositoryFor(sourceType.getType())) { + return false; + } + + if (sourceType.equals(targetType)) { + return true; + } + + Class rawIdType = repositories.getRepositoryInformationFor(sourceType.getType()).getIdType(); + + return targetType.equals(TypeDescriptor.valueOf(rawIdType)) + || conversionService.canConvert(rawIdType, targetType.getType()); + } + } + + @SuppressWarnings("serial") + private static final class ConversionMatchAbbreviationException extends RuntimeException {} } diff --git a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java index d809c2492..480712d24 100644 --- a/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java +++ b/src/test/java/org/springframework/data/repository/support/DomainClassConverterUnitTests.java @@ -45,22 +45,18 @@ import org.springframework.data.repository.core.support.DummyRepositoryFactoryBe public class DomainClassConverterUnitTests { static final User USER = new User(); + static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class); + static final TypeDescriptor USER_TYPE = TypeDescriptor.valueOf(User.class); + static final TypeDescriptor LONG_TYPE = TypeDescriptor.valueOf(Long.class); @SuppressWarnings("rawtypes") DomainClassConverter converter; - TypeDescriptor sourceDescriptor; - TypeDescriptor targetDescriptor; - @Mock DefaultConversionService service; @Before @SuppressWarnings({ "unchecked", "rawtypes" }) public void setUp() { - converter = new DomainClassConverter(service); - - sourceDescriptor = TypeDescriptor.valueOf(String.class); - targetDescriptor = TypeDescriptor.valueOf(User.class); } @Test @@ -96,19 +92,19 @@ public class DomainClassConverterUnitTests { * @see DATACMNS-233 */ public void returnsNullForNullSource() { - assertThat(converter.convert(null, sourceDescriptor, targetDescriptor), is(nullValue())); + assertThat(converter.convert(null, STRING_TYPE, USER_TYPE), is(nullValue())); } /** * @see DATACMNS-233 */ public void returnsNullForEmptyStringSource() { - assertThat(converter.convert("", sourceDescriptor, targetDescriptor), is(nullValue())); + assertThat(converter.convert("", STRING_TYPE, USER_TYPE), is(nullValue())); } private void assertMatches(boolean matchExpected) { - assertThat(converter.matches(sourceDescriptor, targetDescriptor), is(matchExpected)); + assertThat(converter.matches(STRING_TYPE, USER_TYPE), is(matchExpected)); } @Test @@ -120,7 +116,7 @@ public class DomainClassConverterUnitTests { when(service.canConvert(String.class, Long.class)).thenReturn(true); when(service.convert(anyString(), eq(Long.class))).thenReturn(1L); - converter.convert("1", sourceDescriptor, targetDescriptor); + converter.convert("1", STRING_TYPE, USER_TYPE); UserRepository bean = context.getBean(UserRepository.class); UserRepository repo = (UserRepository) ((Advised) bean).getTargetSource().getTarget(); @@ -141,20 +137,53 @@ public class DomainClassConverterUnitTests { when(service.canConvert(String.class, Long.class)).thenReturn(true); converter.setApplicationContext(context); - assertThat(converter.matches(sourceDescriptor, targetDescriptor), is(true)); + assertThat(converter.matches(STRING_TYPE, USER_TYPE), is(true)); } /** - * @DATACMNS-583 + * @see DATACMNS-583 */ @Test public void shouldReturnSourceObjectIfSourceAndTargetTypesAreTheSame() { - ApplicationContext context = initContextWithRepo(); - converter.setApplicationContext(context); + converter.setApplicationContext(initContextWithRepo()); - assertThat(converter.matches(targetDescriptor, targetDescriptor), is(true)); - assertThat((User) converter.convert(USER, targetDescriptor, targetDescriptor), is(USER)); + assertThat(converter.matches(USER_TYPE, USER_TYPE), is(true)); + assertThat((User) converter.convert(USER, USER_TYPE, USER_TYPE), is(USER)); + } + + /** + * @see DATACMNS-627 + */ + @Test + public void supportsConversionFromIdType() { + + converter.setApplicationContext(initContextWithRepo()); + + assertThat(converter.matches(LONG_TYPE, USER_TYPE), is(true)); + } + + /** + * @see DATACMNS-627 + */ + @Test + public void supportsConversionFromEntityToIdType() { + + converter.setApplicationContext(initContextWithRepo()); + + assertThat(converter.matches(USER_TYPE, LONG_TYPE), is(true)); + } + + /** + * @see DATACMNS-627 + */ + @Test + public void supportsConversionFromEntityToString() { + + converter.setApplicationContext(initContextWithRepo()); + + when(service.canConvert(Long.class, String.class)).thenReturn(true); + assertThat(converter.matches(USER_TYPE, STRING_TYPE), is(true)); } private ApplicationContext initContextWithRepo() {