How to spring inject configuration value for Joda Period - spring

How can I use the #Value annotation to configure a Joda-Time Period field in my spring bean?
E.g. Given the following component class:
#Component
public class MyService {
#Value("${myapp.period:P1D}")
private Period periodField;
...
}
I want to use standard ISO8601 format to define the period in a properties file.
I get this error:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [org.joda.time.Period]: no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:302)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:125)
at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:61)
... 35 more

A simple solution that does not require any java code is to use Spring Expression Language (SpEL).
(My example uses java.time.Duration and not Joda stuff but I think you get it anyway.)
#Value("#{T(java.time.Duration).parse('${notifications.maxJobAge}')}")
private Duration maxJobAge;

What you can do is register a Spring ConversionService bean and implement a proper converter.
#Bean
public ConversionServiceFactoryBean conversionService() {
ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
Set<Converter<?, ?>> myConverters = new HashSet<>();
myConverters.add(new StringToPeriodConverter());
conversionServiceFactoryBean.setConverters(myConverters);
return conversionServiceFactoryBean;
}
public class StringToPeriodConverter implements Converter<String, Period> {
#Override
public Period convert(String source) {
return Period.parse(source);
}
}

Another, not elegant, option, is to use a String setter who invokes the parse method.
#Value("${myapp.period:P1D}")
public void setPeriodField(String periodField)
{
if (isBlank(periodField))
this.periodField= null;
this.periodField= Duration.parse(periodField);
}

For joda-time:2.10.13 and spring-boot:2.3.2.RELEASE next example (like in question) is worked:
#Value("${myapp.period:P1D}")
private Period periodField;
If you use java.time.Period, in addition, worked a simple period property format (org.springframework.boot.convert.PeriodStyle):
#Value("${myapp.period:1d}")
private Period periodField;

Related

Spring Autowire configuration in flink

i am trying to use the comination of flink and springboot and im having some problems.
Lets say i am having this flow.
Getting json string that have one field date that contains date string.
using map function and ObjectMapper to parse it into object of LocalDateTime
print
This is simple usecase that will describe my probem.
So, i have Word Class represnting Word that contains LocalDateTime field.
#Data
public class Word {
#JsonDeserialize(using = LocalDateTimeSerde.class)
LocalDateTime date;
}
The LocalDateTimeDeserlization is looking like that(I want to autowire the app configuration):
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
#JsonComponent
public class LocalDateTimeSerde extends JsonDeserializer<LocalDateTime> {
private final AppConf conf;
#Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(this.conf.getDateFormatter());
return LocalDateTime.parse(jsonParser.getText(), formatter);
}
}
AppConf.java represneting the configuration of the application is:
#Data
#Configuration
#ConfigurationProperties(value = "app")
public class AppConf {
private String dateFormatter;
}
DemoApplication.java:
final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment(1);
String example = "{\"date\":\"2019-01-29 00:00\"}";
var stream = env
.fromElements(example)
.map(x->new ObjectMapper().readValue(x,Word.class))
.returns(Word.class);
stream.print();
env.execute("Demo App");
The exception im getting is :
Caused by: java.lang.IllegalArgumentException: Class com.example.demo.LocalDateTimeSerde has no default (no arg) constructor
The main problem here is that the code of the deserialization is running on the TaskManager and over there springboot doesnt take a part, so it doesn`t inject AppConf into the class.
Adding #NoArgsConstructor will not solve the problem
I think i know why it is hapenning (because flink master serialize the classes to the workers and then springboot doesn`t "ScanComponents" and takes control.
Is there any solution for that? I really want to combine spring with flink also in the worker`s function.
Thanks.
In general, I personally don't think it's a good idea to mix those concepts. The easiest solution is to use AutoWired only on the job manager and use explicit dependency injection when you go into Flink-land.
For example, you could extract the date pattern in the DemoApplication and set it on the ObjectMapper. (Don't forget to initialize ObjectMapper only once in your real code!)
If you really want to use AutoWiring. I guess you need to manually trigger the autowiring on taskmanager. There is a related post specifically for ObjectMapper.

Spring Boot WebFlux Converter

I am trying to migrate my project from the Spring MVC to the Spring WebFlux.
The repository I am currently using is ReactiveCrudRepository.
In order to achieve the post-redirect-get pattern, which I have used within Spring MVC, I need to rewrite the current converter to work with ReactiveCrudRepository.
I was trying to do that with this aproach:
#Component
public class ObjByIdConverter implements Converter<String, Obj> {
#Autowired
private IObjRepository objRepository;
#Override
public Obj convert(String id) {
return objRepository.findById(id).block();
}
}
When I implement converter in this way, I am getting the following error:
block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-xxx.
When I was using CrudRepository instead of ReactiveCrudRepository everything was worked fine.
Is there a way to implement converter to work with ReactiveCrudRepository?
~~~ Edit 1 ~~~
The controller class:
#PostMapping
public Mono<String> processOrder(#ModelAttribute("newCar") Car car) {
webDataBinder.validate();
BindingResult bindingResult = webDataBinder.getBindingResult();
if (bindingResult.hasErrors()) {
return Mono.just("orderForm");
}
return this.carRepository.save(car).thenReturn("redirect:/");
}
The model class:
#Document(collection = "cars")
#ToString
#EqualsAndHashCode
public class Car {
#Id
private String id;
private List<Obj> objs = new ArrayList<>();
// constructor, getters, setters, ...
}
I am using the Thymeleaf view technology.
I have to provide the implementation for ObjByIdConverter because I am getting the following error message: [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'objs'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.example.app.model.Obj' for property 'objs[0]': no matching editors or conversion strategy found]
You should not use block in any case in reactive development. If you have ReactiveRepository and Spring Webflux, use them together with Mono/Flux from repository to controller to leverage the reactive way of doing.
But I think the main reason why you try to convert the result to a standard type is for the post-redirect-get pattern, could you detail this in the spring controller context ?

Dependency-inject "dynamically specified" beans based on annotation arguments

I have a use case where it would be extraordinarily nice to dynamically instantiate beans (using some kind of factory approach) based on annotation-arguments at the injection point. Specifically, I need to be able to specify a type-argument to the bean-creating factory.
A pretty relevant example would be a JSON deserializer that needs the type which it needs to deserialize to.
I envision either:
#Inject
#DeserializeQualifier(Car.class)
private Deserializer<Car> _carDeserializer;
#Inject
#DeserializeQualifier(Bus.class)
private Deserializer<Bus> _busDeserializer;
.. or simply, if it was possible to sniff the type from the generic type argument:
#Inject
private Deserializer<Car> _carDeserializer;
#Inject
private Deserializer<Bus> _busDeserializer;
The big point here is that I would not know beforehand which types was needed in the project, as this would be a generic tool that many projects would include. So you would annotate your #Configuration class with #EnableDeserializer and could then inject any type deserializer (The factory that makes these deserializers can handle any type, but to be able create one, it would need to know the desired type of the deserialized object - plain generics would not cut it, since Java ain't using reified generics).
So, I'd need to be able to inject into the spring context, or using any other Spring magic tricks, some kind of DeserializerFactory that takes the type argument.
Basically, I need to have Spring invoke the following method based based on either, as in the first example, the qualifier argument (or the entire DeserializeQualifier-instance for that matter), or as in the second example, the generic type argument:
DeserializerFactory {
<T> Deserializer<T> createDeserializer(Class<T> type) { ... }
}
You could create a BeanFactoryPostProcessor to set attributes annotated with a custom annotation. I've set up a small Spring Boot project to play around:
// Custom annotation
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface InjectSomeClassHere {
Class value();
}
// Demo bean
#Component
public class SomeBean {
#InjectSomeClassHere(String.class)
private Class someValue;
public Class getInjectedClass() {
return someValue;
}
}
// The BeanFactoryPostProcessor
#Component
public class SomeBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Arrays
.stream(beanFactory.getBeanDefinitionNames())
.filter(beanName -> hasAnnotatedField(beanFactory, beanName))
.forEach(beanName -> {
Object bean = beanFactory.getBean(beanName);
Stream.of(bean.getClass().getDeclaredFields()).forEach(field -> setFieldValue(bean, field));
});
}
private boolean hasAnnotatedField(ConfigurableListableBeanFactory beanFactory, String beanName) {
try {
String className = beanFactory.getBeanDefinition(beanName).getBeanClassName();
if (className == null) {
return false;
}
return Arrays.stream(Class.forName(className).getDeclaredFields())
.anyMatch(field -> field.isAnnotationPresent(InjectSomeClassHere.class));
} catch (ClassNotFoundException e) {
// Error handling here
return false;
}
}
private void setFieldValue(Object filteredBean, Field field) {
try {
// Note: field.isAccessible() is deprecated
if (!field.isAccessible()) {
field.setAccessible(true);
}
// Retrieve the value from the annotation and set the field
// In your case, you could call `createDeserializer(fieldValue);` and set the field using the return value.
// Note that you should change the type of `SomeBean#someValue` accordingly.
Class fieldValue = field.getAnnotation(InjectSomeClassHere.class).value();
field.set(filteredBean, fieldValue);
} catch (IllegalAccessException e) {
// Error handling here
e.printStackTrace();
}
}
}
// A small test to verify the outcome of the BeanFactoryPostProcessor
#RunWith(SpringRunner.class)
#SpringBootTest
public class SomeBeanTests {
#Autowired
private SomeBean someBean;
#Test
public void getInjectedClass_shouldHaveStringClassInjected() {
Assert.assertEquals(String.class, someBean.getInjectedClass());
}
}
Please note that this is a very naive implementation and requires further fine tuning. For instance, it scans all attributes in all spring components for the presence of an annotation.
Good luck with your project!

spring ldap 2.0.1 replacing deprecated OdmManager

It seems in Spring LDAP 2.x, that the OdmManager facility is deprecated, as most odm-like things can be done by ldapTemplate, which is true. But the OdmManager has the ability to inject a ConverterManager which can be told about your custom type conversions. What is the equivalent method for using ldapTemplate for odm-like (ConverterManager) operations ?
If there is not an equivalent system in ldapTemplate, should it :
implicitly detect custom classes with single string constructors and String toString() class methods, iff they exist as properties to be mapped to/from ldap attributes.
implicitly allow the use of bean editors, to map from text to the specific type
explicitly have some facility like a Converter manager, in which you can configure this.
As an example, consider the simple class (which i would like to be the type of a bean property, which will be mapped to a ldap timestamp)
public class LdapTimestamp {
static private Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Australia/Brisbane"));
static private DateFormat toStringFormat;
static {
toStringFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
toStringFormat.setCalendar(cal);
}
static private DateFormat nativeLdapFormat = new SimpleDateFormat("yyyyMMddHHmmssZ");
private Date dateTime; // GMT time
public LdapTimestamp(String ldapDateTimeString) throws ParseException {
this.dateTime = nativeLdapFormat.parse(ldapDateTimeString);
}
public LdapTimestamp() {
super();
}
public Date getDateTime() {
return dateTime;
}
public void setDateTime(Date dateTimeObject) {
this.dateTime = dateTimeObject;
}
public void setDateTime(String ldapDateTimeString) throws ParseException {
this.dateTime = nativeLdapFormat.parse(ldapDateTimeString);
}
public String toStringShort() {
return toStringFormat.format(dateTime);
}
public String toString() {
return nativeLdapFormat.format(dateTime);
}
}
The intent is that the bean natively store a Date object, which can be used for date range comparisons and the like, while returning the ldap date string outwards of the bean as toString() and inward to the bean, as constructor with a single String argument.
This seems to be what is suggested with ConverterManager, but this is new code, so i'd rather not use the deprecated OdmManager interface if i can avoid it. ConverterManager is not deprecated, but i can't see an obvious way of linking it to ldapTemplate to use.
Any thoughts would be welcome.
LdapTemplate has a setObjectDirectoryMapper method, which enables you to inject a configured ObjectDirectoryMapper (which corresponds to the OdmManager in previous versions). DefaultObjectDirectoryMapper can be configured with a ConverterManager, so I think you should be all set.

Spring Data Neo4j: Converter of object to string works, but object to long is not executed

I have a really strange issue with converting from domain objects to those Neo4j can natively store as property value. As a test case I use Joda's DateTime. A object of that type can be converted to a String or Long quite easily.
The conversion from DateTime to String works flawlessly with this code:
public class DateTimeToStringConverter implements Converter<DateTime, String> {
#Override
public String convert(DateTime source) {
return source.toDateTimeISO().toString();
}
}
The property shows up in the node:
Node[1] {
'__type__' = '...',
'entityEditedAt' = '2012-12-28T12:32:50.308+01:00',
'entityCreatedAt' = '2012-12-28T12:32:50.297+01:00',
...
}
However if I like to save the DateTime as Long (useful to sort by time in Cypher), it does not work at all. Here is my converter:
public class DateTimeToLongConverter implements Converter<DateTime, Long> {
#Override
public Long convert(DateTime source) {
return source.toDateTimeISO().getMillis();
}
}
The property is not saved on the node. Thus it is missing completely. No exception is thrown. It seems like the conversion code is not called at all.
The converters are hooked to Spring Data using code based configuration:
#Bean
public ConversionServiceFactoryBean conversionService() {
Set converters = Sets.newHashSet();
// These work!
converters.add(new DateTimeToStringConverter());
converters.add(new StringToDateTimeConverter());
// These don't :-(
//converters.add(new DateTimeToLongConverter());
//converters.add(new LongToDateTimeConverter());
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.setConverters(converters);
return bean;
}
Any clues? I'm quite lost here, as it should work in my opinion...
Edit
I found following text in the Spring Data Neo4j documentation:
All fields convertible to a String using the Spring conversion services will be stored as a string.
Does this mean, that only conversions to string are supported? This seems rather limiting.
Tell SDN that you want to store your joda DateTime property as a long with:
#NodeEntity
public class MyEntity {
...
#GraphProperty(propertyType = Long.class)
private DateTime timestamp;
....
}
Then your registered DateTimeToLongConverter will kick in.

Resources