-
Notifications
You must be signed in to change notification settings - Fork 687
DATACMNS-1034 - Introduced API to easily register bidirectional, type-based converters. #209
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
Changes from all commits
dc14487
e9438ed
3436c63
20d2f43
04a148a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* | ||
* Copyright 2017 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.convert; | ||
|
||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.function.Function; | ||
|
||
import org.springframework.core.convert.converter.GenericConverter; | ||
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; | ||
import org.springframework.util.Assert; | ||
|
||
/** | ||
* API to easily set up {@link GenericConverter} instances using Java 8 lambdas, mostly in bidirectional fashion for | ||
* easy registration as custom type converters of the Spring Data mapping subsystem. The registration starts either with | ||
* the definition of a reading or writing converter that can then be completed. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
* @see #reading(Class, Class, Function) | ||
* @see #writing(Class, Class, Function) | ||
* @soundtrack John Mayer - Moving On and Getting Over (The Search for Everything) | ||
*/ | ||
public interface ConverterBuilder { | ||
|
||
/** | ||
* Creates a new {@link ReadingConverterBuilder} to produce a converter to read values of the given source (the store | ||
* type) into the given target (the domain type). | ||
* | ||
* @param source must not be {@literal null}. | ||
* @param target must not be {@literal null}. | ||
* @param function must not be {@literal null}. | ||
* @return | ||
*/ | ||
static <S, T> ReadingConverterBuilder<S, T> reading(Class<S> source, Class<T> target, | ||
Function<? super S, ? extends T> function) { | ||
|
||
Assert.notNull(source, "Source type must not be null!"); | ||
Assert.notNull(target, "Target type must not be null!"); | ||
Assert.notNull(function, "Conversion function must not be null!"); | ||
|
||
return new DefaultConverterBuilder<>(new ConvertiblePair(source, target), Optional.empty(), Optional.of(function)); | ||
} | ||
|
||
/** | ||
* Creates a new {@link WritingConverterBuilder} to produce a converter to write values of the given source (the | ||
* domain type) into the given target (the store type). | ||
* | ||
* @param source must not be {@literal null}. | ||
* @param target must not be {@literal null}. | ||
* @param function must not be {@literal null}. | ||
* @return | ||
*/ | ||
static <S, T> WritingConverterBuilder<S, T> writing(Class<S> source, Class<T> target, | ||
Function<? super S, ? extends T> function) { | ||
|
||
Assert.notNull(source, "Source type must not be null!"); | ||
Assert.notNull(target, "Target type must not be null!"); | ||
Assert.notNull(function, "Conversion function must not be null!"); | ||
|
||
return new DefaultConverterBuilder<>(new ConvertiblePair(target, source), Optional.of(function), Optional.empty()); | ||
} | ||
|
||
/** | ||
* Returns all {@link GenericConverter} instances to be registered for the current {@link ConverterBuilder}. | ||
* | ||
* @return | ||
*/ | ||
Set<GenericConverter> getConverters(); | ||
|
||
/** | ||
* Exposes a writing converter. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
*/ | ||
interface WritingConverterAware { | ||
|
||
/** | ||
* Returns the writing converter already created. | ||
* | ||
* @return | ||
*/ | ||
GenericConverter getWritingConverter(); | ||
} | ||
|
||
/** | ||
* Exposes a reading converter. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
*/ | ||
interface ReadingConverterAware { | ||
|
||
/** | ||
* Returns the reading converter already created. | ||
* | ||
* @return | ||
*/ | ||
GenericConverter getReadingConverter(); | ||
} | ||
|
||
/** | ||
* Interface to represent an intermediate setup step of {@link ConverterAware} defining a reading converter first. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
*/ | ||
interface ReadingConverterBuilder<T, S> extends ConverterBuilder, ReadingConverterAware { | ||
|
||
/** | ||
* Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. | ||
* | ||
* @param function must not be {@literal null}. | ||
* @return | ||
*/ | ||
ConverterAware andWriting(Function<? super S, ? extends T> function); | ||
} | ||
|
||
/** | ||
* Interface to represent an intermediate setup step of {@link ConverterAware} defining a writing converter first. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
*/ | ||
interface WritingConverterBuilder<S, T> extends ConverterBuilder, WritingConverterAware { | ||
|
||
/** | ||
* Creates a new {@link ConverterAware} by registering the given {@link Function} to add a write converter. | ||
* | ||
* @param function must not be {@literal null}. | ||
* @return | ||
*/ | ||
ConverterAware andReading(Function<? super T, ? extends S> function); | ||
} | ||
|
||
/** | ||
* A {@link ConverterBuilder} aware of both a reading and writing converter. | ||
* | ||
* @author Oliver Gierke | ||
* @since 2.0 | ||
*/ | ||
interface ConverterAware extends ConverterBuilder, ReadingConverterAware, WritingConverterAware {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,9 +37,11 @@ | |
import org.springframework.core.GenericTypeResolver; | ||
import org.springframework.core.convert.converter.Converter; | ||
import org.springframework.core.convert.converter.ConverterFactory; | ||
import org.springframework.core.convert.converter.ConverterRegistry; | ||
import org.springframework.core.convert.converter.GenericConverter; | ||
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair; | ||
import org.springframework.core.convert.support.GenericConversionService; | ||
import org.springframework.data.convert.ConverterBuilder.ConverterAware; | ||
import org.springframework.data.mapping.model.SimpleTypeHolder; | ||
import org.springframework.data.util.Optionals; | ||
import org.springframework.data.util.Streamable; | ||
|
@@ -90,10 +92,12 @@ public class CustomConversions { | |
/** | ||
* Creates a new {@link CustomConversions} instance registering the given converters. | ||
* | ||
* @param converters | ||
* @param storeConversions must not be {@literal null}. | ||
* @param converters must not be {@literal null}. | ||
*/ | ||
public CustomConversions(StoreConversions storeConversions, List<?> converters) { | ||
public CustomConversions(StoreConversions storeConversions, Collection<?> converters) { | ||
|
||
Assert.notNull(storeConversions, "StoreConversions must not be null!"); | ||
Assert.notNull(converters, "List of converters must not be null!"); | ||
|
||
this.readingPairs = new LinkedHashSet<>(); | ||
|
@@ -149,33 +153,46 @@ public boolean isSimpleType(Class<?> type) { | |
* | ||
* @param conversionService | ||
*/ | ||
public void registerConvertersIn(GenericConversionService conversionService) { | ||
public void registerConvertersIn(ConverterRegistry conversionService) { | ||
|
||
Assert.notNull(conversionService, "ConversionService must not be null!"); | ||
|
||
converters.forEach(it -> { | ||
converters.forEach(it -> registerConverterIn(it, conversionService)); | ||
} | ||
|
||
boolean added = false; | ||
/** | ||
* Registers the given converter in the given {@link GenericConversionService}. | ||
* | ||
* @param candidate must not be {@literal null}. | ||
* @param conversionService must not be {@literal null}. | ||
*/ | ||
private void registerConverterIn(Object candidate, ConverterRegistry conversionService) { | ||
|
||
if (it instanceof Converter) { | ||
conversionService.addConverter(Converter.class.cast(it)); | ||
added = true; | ||
} | ||
boolean added = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found it easier to read (understand) multiple
so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At a quick glance, yes. Not at a second. The approach used allows the instance to implement multiple interfaces and thus obtains all Not sure this is still needed as I am unsure how likely this is in the first place. I just kept it around to preserve behavioural consistency to the old |
||
|
||
if (it instanceof ConverterFactory) { | ||
conversionService.addConverterFactory(ConverterFactory.class.cast(it)); | ||
added = true; | ||
} | ||
if (candidate instanceof Converter) { | ||
conversionService.addConverter(Converter.class.cast(candidate)); | ||
added = true; | ||
} | ||
|
||
if (it instanceof GenericConverter) { | ||
conversionService.addConverter(GenericConverter.class.cast(it)); | ||
added = true; | ||
} | ||
if (candidate instanceof ConverterFactory) { | ||
conversionService.addConverterFactory(ConverterFactory.class.cast(candidate)); | ||
added = true; | ||
} | ||
|
||
if (!added) { | ||
throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, it)); | ||
} | ||
}); | ||
if (candidate instanceof GenericConverter) { | ||
conversionService.addConverter(GenericConverter.class.cast(candidate)); | ||
added = true; | ||
} | ||
|
||
if (candidate instanceof ConverterAware) { | ||
ConverterAware.class.cast(candidate).getConverters().forEach(it -> registerConverterIn(it, conversionService)); | ||
added = true; | ||
} | ||
|
||
if (!added) { | ||
throw new IllegalArgumentException(String.format(NOT_A_CONVERTER, candidate)); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -460,10 +477,15 @@ public Streamable<ConverterRegistration> getRegistrationsFor(Object converter) { | |
boolean isWriting = type.isAnnotationPresent(WritingConverter.class); | ||
boolean isReading = type.isAnnotationPresent(ReadingConverter.class); | ||
|
||
if (converter instanceof GenericConverter) { | ||
if (converter instanceof ConverterAware) { | ||
|
||
return Streamable.of(() -> ConverterAware.class.cast(converter).getConverters().stream()// | ||
.flatMap(it -> getRegistrationsFor(it).stream())); | ||
|
||
} else if (converter instanceof GenericConverter) { | ||
|
||
GenericConverter genericConverter = (GenericConverter) converter; | ||
return Streamable.of(genericConverter.getConvertibleTypes()).map(it -> register(it, isReading, isWriting)); | ||
return Streamable.of(GenericConverter.class.cast(converter).getConvertibleTypes())// | ||
.map(it -> register(it, isReading, isWriting)); | ||
|
||
} else if (converter instanceof ConverterFactory) { | ||
|
||
|
@@ -474,7 +496,7 @@ public Streamable<ConverterRegistration> getRegistrationsFor(Object converter) { | |
return getRegistrationFor(converter, Converter.class, isReading, isWriting); | ||
|
||
} else { | ||
throw new IllegalArgumentException("Unsupported converter type!"); | ||
throw new IllegalArgumentException(String.format("Unsupported converter type %s!", converter)); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
<S, T>
?