Spring Data JPA method + REST: Enum to Integer conversion - java

I've got an endpoint:
/api/offers/search/findByType?type=X
where X should be an Integer value (an ordinal value of my OfferType instance), whereas Spring considers X a String and will be applying its StringToEnumConverterFactory with the StringToEnum convertor.
public interface OfferRepository extends PagingAndSortingRepository<Offer, Long> {
List<Offer> findByType(#Param("type") OfferType type);
}
So I wrote a custom Converter<Integer, OfferType> which simply get a instance by the given ordinal number:
public class IntegerToOfferTypeConverter implements Converter<Integer, OfferType> {
#Override
public OfferType convert(Integer source) {
return OfferType.class.getEnumConstants()[source];
}
}
Then I registered it properly with a Configuration:
#EnableWebMvc
#Configuration
#RequiredArgsConstructor
public class GlobalMVCConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new IntegerToOfferTypeConverter());
}
}
And I was expected that all requests to findByType?type=X will pass through my converter, but they do not.
Is any way to say that all enums defined as a request parameters have to be provided as an Integer? Furthermore, is any way to say it globally, not just for a specific enum?
EDIT: I've found IntegerToEnumConverterFactory in my classpath that does all I need. And it is registered with DefaultConversionService which is a default service for conversion. How can that be applied?
EDIT2: It's such a trivial thing, I was wondering if there is a property to turn enum conversion on.
EDIT3: I tried to write a Converter<String, OfferType> after I had got String from TypeDescriptor.forObject(value), it didn't help.
EDIT4: My problem was that I had placed custom converter registration into a MVC configuration (WebMvcConfigurerAdapter with addFormatters) instead of a REST Repositories one (RepositoryRestConfigurerAdapter with configureConversionService).

Spring parses the query parameters as Strings. I believe it always uses Converter<String, ?> converters to convert from the query parameters to your repository methods parameters. It uses an enhanced converter service, since it registers its own converters such as Converter<Entity, Resource>.
Therefore you have to create a Converter<String, OfferType>, e.g.:
#Component
public class StringToOfferTypeConverter implements Converter<String, OfferType> {
#Override
public OfferType convert(String source) {
return OfferType.class.getEnumConstants()[Integer.valueOf(source)];
}
}
And then configure this converter to be used by the Spring Data REST, in a class extending RepositoryRestConfigurerAdapter:
#Configuration
public class ConverterConfiguration extends RepositoryRestConfigurerAdapter {
#Autowired
StringToOfferTypeConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
I tried to add this to the basic tutorial, added a simple enum to the Person class:
public enum OfferType {
ONE, TWO;
}
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private OfferType type;
public OfferType getType() {
return type;
}
public void setType(OfferType type) {
this.type = type;
}
}
And when I call:
http://localhost:8080/people/search/findByType?type=1
I get the result without errors:
{
"_embedded" : {
"people" : [ ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/search/findByType?type=1"
}
}
}
To implement a global Enum converter, you have to create a factory and register it in the configuration using the method: conversionService.addConverterFactory(). The code below is a modified example from the documentation:
public class StringToEnumFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnum(targetType);
}
private final class StringToEnum<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnum(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
Integer index = Integer.valueOf(source);
return enumType.getEnumConstants()[index];
}
}
}

Related

generic attribute in java with multiple fields

i have a question. do you know how can i use the same field with différent generic attribut implementation.
I have an interface for modelMapper that i am using to generalise the Type of objects that will be mapped
public interface IMapper<S, D> {
D map(S src, Class destination);
}
I also have this implementation of this interface :
#Component
public class ModelMapperImpl<S,D> implements IMapper<S,D> {
#Autowired
private ModelMapper mapper;
#Override
public D map(S src, Class destination) {
return (D) mapper.map(src, destination);
}
}
The problem is this, i need to have for each mapping a field in my class which is not a good practice i think and i am searching if there is a way to have only one generic field for all my type of mappings
#Service
public class UserService {
private IMapper<AddressDTO, Address> mapperAddress;
private IMapper<UsersDTO, Users> mapperUser; // i want to have only one IMapper field
is there is a way to do That ? thank you guys for your help.
I'll assume you are trying to make it easy to change the mapping library(moving from ModelMapper to something else, if it's needed). Then you can make the method generic, not the class.
public interface IMapper {
<S, D> D map(S src, Class<D> destination);
}
Impl:
#Component
public class ModelMapperImpl implements IMapper {
#Autowired
private ModelMapper mapper;
#Override
public <S, D> D map(S src, Class<D> destinationClass) {
return mapper.map(src, destinationClass);
}
}
Now you need only one IMapper in your service.
#Service
public class UserService {
#Autowired
private IMapper mapper;
}

How to create custom conversions for Spring Data Cassandra?

I'm not able to create custom conversions in order to use Currency and Locale as data types.
I'm using a #Configuration annotated class which will be auto-configured with META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.acme.autoconfigure.ConverterAutoConfiguration
I tried registering the converters directly as beans and also tried to create CassandraCustomConversions bean without success:
#Configuration
public class ConverterAutoConfiguration {
/*
#Bean
public Converter<String, Currency> currencyReadConverter() {
return new Converter<String, Currency>() {
#Override
public Currency convert(String source) {
return Currency.getInstance(source);
}
};
}
#Bean
public Converter<Currency, String> currencyWriteConverter() {
return new Converter<Currency, String>() {
#Override
public String convert(Currency source) {
return source.toString();
}
};
}
#Bean
public Converter<String, Locale> localeReadConverter() {
return new Converter<String, Locale>() {
#Override
public Locale convert(String source) {
return StringUtils.parseLocaleString(source);
}
};
}
#Bean
public Converter<Locale, String> localeWriteConverter() {
return new Converter<Locale, String>() {
#Override
public String convert(Locale source) {
return source.toString();
}
};
}
*/
#Bean
public CassandraCustomConversions cassandraCustomConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(CurrencyReadConverter.INSTANCE);
converters.add(CurrencyWriteConverter.INSTANCE);
converters.add(LocaleReadConverter.INSTANCE);
converters.add(LocaleWriteConverter.INSTANCE);
return new CassandraCustomConversions(converters);
}
enum CurrencyReadConverter implements Converter<String, Currency> {
INSTANCE;
#Override
public Currency convert(String source) {
return Currency.getInstance(source);
}
}
enum CurrencyWriteConverter implements Converter<Currency, String> {
INSTANCE;
#Override
public String convert(Currency source) {
return source.toString();
}
}
enum LocaleReadConverter implements Converter<String, Locale> {
INSTANCE;
#Override
public Locale convert(String source) {
return StringUtils.parseLocaleString(source);
}
}
enum LocaleWriteConverter implements Converter<Locale, String> {
INSTANCE;
#Override
public String convert(Locale source) {
return source.toString();
}
}
}
With the CassandraCustomConversions bean I'm getting an exception directly at startup:
Caused by: org.springframework.data.mapping.MappingException: Cannot resolve DataType for type [class java.lang.String] for property [categoryId] in entity [com.acme.model.Category]; Consider registering a Converter or annotating the property with #CassandraType.
It seems its loosing all the default mappings.
When using the converter beans directly I'm getting the following exception:
org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract reactor.core.publisher.Flux com.acme.repository.CategoryLocaleReactiveRepository.findByUriNameInAndKeyLocale(java.util.Collection,java.util.Locale)! Reason: Could not inline literal of type java.util.Locale. This happens because the driver doesn't know how to map it to a CQL type. Try passing a TypeCodec or CodecRegistry to literal().
Based on this issue this should be possible somehow: https://github.com/spring-projects/spring-boot/issues/8411
Try adding #WritingConverter and #ReadingConverter to your converters. I believe spring struggles a bit in deciding which converter to use with non custom types.
I created a local project and managed to get the conversion of Locale and Currency working once those 2 annotations were added to the appropriate converter.
I can push my test project to Github and share if you are still having issues.
Reference: https://docs.spring.io/spring-data/cassandra/docs/current/reference/html/#customconversions.java
Example Project: https://github.com/michaelmcfadyen/spring-data-cassandra-custom-converter-example

Spring REST validation on custom annotation

I'm trying to add some extra validation logic on my REST beans using annotations. This is just an example, but the point is that the annotation is to be used on multiple REST resource objects / DTO's.
I was hoping for a solution like this:
public class Entity {
#NotNull // JSR-303
private String name;
#Phone // Custom phonenumber that has to exist in a database
private String phoneNumber;
}
#Component
public class PhoneNumberValidator implements Validator { // Spring Validator
#Autowired
private PhoneRepository repository;
public boolean supports(Class<?> clazz) {
return true;
}
public void validate(Object target, Errors errors) {
Phone annotation = // find fields with annotations by iterating over target.getClass().getFields().getAnnotation
Object fieldValue = // how do i do this? I can easily get the annotation, but now I wish to do a call to repository checking if the field value exists.
}
}
Did you try JSR 303 bean validator implementations like hibernate validator
e.g. is available here http://www.codejava.net/frameworks/spring/spring-mvc-form-validation-example-with-bean-validation-api
Maven Module A:
public interface RestValidator<A extends Annotation, T> extends ConstraintValidator<A, T>
public interface PhoneValidator extends RestValidator<PhoneNumber, String>
#Target(FIELD)
#Retention(RUNTIME)
#Constraint(validatedBy = PhoneValidator.class) // This usually doesnt work since its a interface
public #interface PhoneNumber {
// JSR-303 required fields (payload, message, group)
}
public class Person {
#PhoneNumber
private String phoneNumber;
}
Maven Module B:
#Bean
LocalValidatorFactoryBean configurationPropertiesValidator(ApplicationContext context, AutowireCapableBeanFactory factory) {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setConstraintValidatorFactory(factory(context, factory));
return factoryBean;
}
private ConstraintValidatorFactory factory(final ApplicationContext context, final AutowireCapableBeanFactory factory) {
return new ConstraintValidatorFactory() {
#Override
public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
if (RestValidator.class.isAssignableFrom(key)) {
return context.getBean(key);
} else {
return factory.createBean(key);
}
}
#Override
public void releaseInstance(ConstraintValidator<?, ?> instance) {
if (!(instance instanceof RestValidator<?, ?>)) {
factory.destroyBean(instance);
}
}
};
}
#Bean
WebMvcConfigurerAdapter webMvcConfigurerAdapter(final LocalValidatorFactoryBean validatorFactoryBean) {
return new WebMvcConfigurerAdapter() { // Adds the validator to MVC
#Override
public Validator getValidator() {
return validatorFactoryBean;
}
};
}
Then I have a #Component implementation of PhoneValidator that has a Scope = Prototype.
I hate this solution, and I think Spring SHOULD look up on Interface implementations by default, but I'm sure some people that are a lot smarter than me made the decision not to.

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

Inject Spring repositories and their provided class

Is there a way to extract all Repositories as well as the Class<T> they are providing?
I have some Repositories which are annotated with a Qualifier:
#NetworkDataProvider
#Repository
public interface SwitchRepository extends CrudRepository<Switch, SwitchPK>
The beans they provide are annotated with metadata which defines the way they will be displayed in the GUI:
#Entity
#Table(...)
public class Switch implements Serializable {
#Column(name = "switch_name")
#NotNull
#UIName(value = "name of switch")
#UIPrio(value = 2)
private String name;
Now I have to extract all Repositories and their corresponding classes:
#Autowired
#NetworkDataProvider
List<Repository<?>> repositories;
public List<RepositoryClassTuple> getAllNetworkDataProvider() {
return repositories.map(r ->
new RepositoryClassTuple(r, /* how to do this */ r.getProidedClass())).asList();
}
Is there any Way to do this? I really need the Annotations of the repository provided data beans.
Spring Data has a type called Repositories that takes a ListableBeanFactory which can then be used to inspect the repositories:
Repositories repositories = new Repositories(beanFactory);
for (Class<?> domainType : repositories) {
RepositoryInformation info = repositories.getRepositoryInformationFor(domainType);
…
}
I still wonder why you need to mess with this low-level stuff. Really nothing that normal application code should do 🙃.
You can define an interface :
public interface NetworkRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
Class<T> getType();
}
Then you can define your implementions like this :
public interface PersonRepository extends NetworkRepository<Person, Long> {
#Override
default Class<Person> getType() {
return Person.class;
}
}
public interface AnimalRepository extends NetworkRepository<Animal, Long> {
#Override
default Class<Animal> getType() {
return Animal.class;
}
}
Then to get them all :
#Autowired
Collection<NetworkRepository> networkRepositories;
Finally you can use the getType() method to get your class information.
IMPORTANT : you have to declare NetworkRepository in a package not scannable by Spring Data.
You can create an interface named MyRepository
public interface MyRepository{
}
Then, all your repository classes must implement your interface:
#Repository("foo")
public class FooExample implements MyRepository{
}
#Repository("bar")
public class BarExample implements MyRepository{
}
Finally you can have a map of MyRepository beans injected:
#Component
public class ExampleConsumer {
private final Map<String, MyRepository> repositories;
#Autowired
public ExampleConsumer(Map<String, MyRepository> repositories) {
this.examples = examples;
}
}
In this case the map will contain two entries:
"foo" -> FooExample instance
"bar" -> BarExample instance
Another way is to use java Reflection to read the annotation
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
Here there is a tutorial
http://tutorials.jenkov.com/java-reflection/annotations.html

Categories

Resources