/*
* Copyright 2008-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.convert.ConversionService;
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;
/**
* {@link org.springframework.core.convert.converter.Converter} to convert arbitrary input into domain classes managed
* by Spring Data {@link CrudRepository}s. The implementation uses a {@link ConversionService} in turn to convert the
* source type into the domain class' id type which is then converted into a domain class object by using a
* {@link CrudRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class DomainClassConverter<T extends ConversionService & ConverterRegistry>
implements ConditionalGenericConverter, ApplicationContextAware {
private final T conversionService;
private Repositories repositories = Repositories.NONE;
private ToEntityConverter toEntityConverter;
private ToIdConverter toIdConverter;
/**
* Creates a new {@link DomainClassConverter} for the given {@link ConversionService}.
*
* @param conversionService must not be {@literal null}.
*/
public DomainClassConverter(T conversionService) {
Assert.notNull(conversionService, "ConversionService must not be null!");
this.conversionService = conversionService;
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
*/
public Set<ConvertiblePair> 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)
*/
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
return repositories.hasRepositoryFor(targetType.getType())
? toEntityConverter.convert(source, sourceType, targetType)
: toIdConverter.convert(source, sourceType, targetType);
}
/*
* (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) {
return repositories.hasRepositoryFor(targetType.getType()) ? toEntityConverter.matches(sourceType, targetType)
: toIdConverter.matches(sourceType, targetType);
}
/*
* (non-Javadoc)
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
*/
public void setApplicationContext(ApplicationContext context) {
this.repositories = new Repositories(context);
this.toEntityConverter = new ToEntityConverter(this.repositories, this.conversionService);
this.conversionService.addConverter(this.toEntityConverter);
this.toIdConverter = new ToIdConverter();
this.conversionService.addConverter(this.toIdConverter);
}
/**
* Converter to create domain types from any source that can be converted into the domain types identifier type.
*
* @author Oliver Gierke
* @since 1.10
*/
private class ToEntityConverter implements ConditionalGenericConverter {
private final RepositoryInvokerFactory repositoryInvokerFactory;
/**
* Creates a new {@link ToEntityConverter} for the given {@link Repositories} and {@link ConversionService}.
*
* @param repositories must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public ToEntityConverter(Repositories repositories, ConversionService conversionService) {
this.repositoryInvokerFactory = new DefaultRepositoryInvokerFactory(repositories, conversionService);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
*/
@Override
public Set<ConvertiblePair> 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();
RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(domainType);
RepositoryInformation information = repositories.getRequiredRepositoryInformation(domainType);
return invoker.invokeFindById(conversionService.convert(source, information.getIdType())).orElse(null);
}
/*
* (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 (sourceType.isAssignableTo(targetType)) {
return false;
}
Class<?> domainType = targetType.getType();
if (!repositories.hasRepositoryFor(domainType)) {
return false;
}
Optional<RepositoryInformation> repositoryInformation = repositories.getRepositoryInformationFor(domainType);
return repositoryInformation.map(it -> {
Class<?> rawIdType = it.getIdType();
return sourceType.equals(TypeDescriptor.valueOf(rawIdType))
|| conversionService.canConvert(sourceType.getType(), rawIdType);
}).orElseThrow(
() -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));
}
}
/**
* Converter to turn domain types into their identifiers or any transitively convertible type.
*
* @author Oliver Gierke
* @since 1.10
*/
class ToIdConverter implements ConditionalGenericConverter {
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
*/
@Override
public Set<ConvertiblePair> 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<Object, ?> 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 (sourceType.isAssignableTo(targetType)) {
return false;
}
Class<?> domainType = sourceType.getType();
if (!repositories.hasRepositoryFor(domainType)) {
return false;
}
Optional<RepositoryInformation> information = repositories.getRepositoryInformationFor(domainType);
return information.map(it -> {
Class<?> rawIdType = it.getIdType();
return targetType.equals(TypeDescriptor.valueOf(rawIdType))
|| conversionService.canConvert(rawIdType, targetType.getType());
}).orElseThrow(
() -> new IllegalStateException(String.format("Couldn't find RepositoryInformation for %s!", domainType)));
}
}
}